Renamed package to gramophone

This commit is contained in:
Karim Abou Zeid 2015-03-05 16:50:16 +01:00
commit c28a75c61a
95 changed files with 412 additions and 415 deletions

View file

@ -0,0 +1,82 @@
package com.kabouzeid.gramophone;
import android.app.Application;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.preference.PreferenceManager;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
import com.crashlytics.android.Crashlytics;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.util.ImageLoaderUtil;
import io.fabric.sdk.android.Fabric;
/**
* Created by karim on 25.11.14.
*/
public class App extends Application {
private static final String TAG = App.class.getSimpleName();
private MusicPlayerRemote playerRemote;
private int appTheme;
private SharedPreferences defaultSharedPreferences;
private RequestQueue requestQueue;
@Override
public void onCreate() {
super.onCreate();
Fabric.with(this, new Crashlytics());
ImageLoaderUtil.initImageLoader(this);
}
public MusicPlayerRemote getMusicPlayerRemote() {
if (playerRemote == null) {
playerRemote = new MusicPlayerRemote(this);
playerRemote.restorePreviousState();
}
return playerRemote;
}
public int getAppTheme() {
if (appTheme == 0) {
appTheme = getDefaultSharedPreferences().getInt(AppKeys.SP_THEME, R.style.Theme_MaterialMusic);
}
return appTheme;
}
public SharedPreferences getDefaultSharedPreferences() {
if (defaultSharedPreferences == null) {
defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
}
return defaultSharedPreferences;
}
public void setAppTheme(int appTheme) {
this.appTheme = appTheme;
defaultSharedPreferences.edit().putInt(AppKeys.SP_THEME, appTheme).apply();
}
public boolean isTablet() {
return getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
public boolean isInPortraitMode() {
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
}
public void addToVolleyRequestQueue(Request request) {
request.setTag(TAG);
getVolleyRequestQueue().add(request);
}
public RequestQueue getVolleyRequestQueue() {
if (requestQueue == null) {
requestQueue = Volley.newRequestQueue(this);
}
return requestQueue;
}
}

View file

@ -0,0 +1,105 @@
package com.kabouzeid.gramophone.adapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v7.graphics.Palette;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.model.Album;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.Util;
import com.kabouzeid.gramophone.util.ViewUtil;
import com.kabouzeid.gramophone.view.SquareImageView;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import java.util.List;
/**
* Created by karim on 24.11.14.
*/
public class AlbumViewGridAdapter extends ArrayAdapter<Album> {
public static final String TAG = AlbumViewGridAdapter.class.getSimpleName();
private Context context;
private boolean usePalette;
public AlbumViewGridAdapter(Context context, List<Album> objects) {
super(context, R.layout.album_tile, objects);
this.context = context;
usePalette = true;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Album album = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.album_tile, parent, false);
}
final SquareImageView albumArt = (SquareImageView) convertView.findViewById(R.id.album_art);
final TextView title = (TextView) convertView.findViewById(R.id.album_title);
final TextView artist = (TextView) convertView.findViewById(R.id.album_interpret);
final View footer = convertView.findViewById(R.id.footer);
title.setText(album.title);
artist.setText(album.artistName);
ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(album.id).toString(), albumArt, new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
albumArt.setImageDrawable(null);
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
if (usePalette) {
paletteBugFixBlackAndWhite(title, artist, footer);
}
albumArt.setImageResource(R.drawable.default_album_art);
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if (usePalette) {
applyPalette(loadedImage, title, artist, footer);
}
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
}
});
return convertView;
}
private void applyPalette(Bitmap bitmap, final TextView title, final TextView artist, final View footer) {
Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
final Palette.Swatch vibrantSwatch = palette.getVibrantSwatch();
if (vibrantSwatch != null) {
title.setTextColor(vibrantSwatch.getTitleTextColor());
artist.setTextColor(vibrantSwatch.getTitleTextColor());
ViewUtil.animateViewColor(footer, getContext().getResources().getColor(R.color.materialmusic_default_bar_color),
vibrantSwatch.getRgb());
} else {
paletteBugFixBlackAndWhite(title, artist, footer);
}
}
});
}
private void paletteBugFixBlackAndWhite(TextView title, TextView artist, View footer) {
title.setTextColor(Util.resolveColor(context, R.attr.title_text_color));
artist.setTextColor(Util.resolveColor(context, R.attr.caption_text_color));
int defaultBarColor = getContext().getResources().getColor(R.color.materialmusic_default_bar_color);
ViewUtil.animateViewColor(footer, defaultBarColor, defaultBarColor);
}
}

View file

@ -0,0 +1,60 @@
package com.kabouzeid.gramophone.adapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.lastfm.artist.LastFMArtistThumbnailLoader;
import com.kabouzeid.gramophone.model.Artist;
import java.util.List;
/**
* Created by karim on 29.12.14.
*/
public class ArtistViewListAdapter extends ArrayAdapter<Artist> {
private Context context;
public ArtistViewListAdapter(Context context, List<Artist> objects) {
super(context, R.layout.item_artist_view, objects);
this.context = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Artist artist = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.item_artist_view, parent, false);
}
final TextView artistName = (TextView) convertView.findViewById(R.id.artist_name);
final ImageView artistArt = (ImageView) convertView.findViewById(R.id.artist_image);
artistName.setText(artist.name);
artistArt.setImageResource(R.drawable.default_artist_image);
final Object tag = artist.name;
artistArt.setTag(tag);
LastFMArtistThumbnailLoader.loadArtistThumbnail(context, artist.name, new LastFMArtistThumbnailLoader.ArtistThumbnailLoaderCallback() {
@Override
public void onArtistThumbnailLoaded(Bitmap thumbnail) {
if (artistArt.getTag().equals(tag)) {
if (thumbnail != null) {
artistArt.setImageBitmap(thumbnail);
} else {
artistArt.setImageResource(R.drawable.default_artist_image);
}
}
}
});
return convertView;
}
}

View file

@ -0,0 +1,51 @@
package com.kabouzeid.gramophone.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.model.NavigationDrawerItem;
import com.kabouzeid.gramophone.util.Util;
import java.util.List;
/**
* Created by karim on 23.11.14.
*/
public class NavigationDrawerItemAdapter extends ArrayAdapter<NavigationDrawerItem> {
private int currentChecked = -1;
public NavigationDrawerItemAdapter(Context context, int resource, List<NavigationDrawerItem> objects) {
super(context, resource, objects);
}
public void setChecked(int position) {
currentChecked = position;
notifyDataSetChanged();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
NavigationDrawerItem item = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_navigation_drawer, parent, false);
}
TextView title = (TextView) convertView.findViewById(R.id.title);
ImageView icon = (ImageView) convertView.findViewById(R.id.album_art);
title.setText(item.title);
icon.setImageResource(item.imageRes);
if (position == currentChecked) {
title.setTextColor(Util.resolveColor(getContext(), R.attr.colorAccent));
} else {
title.setTextColor(Util.resolveColor(getContext(), R.attr.title_text_color));
}
View container = convertView.findViewById(R.id.container);
container.setActivated(position == currentChecked);
return convertView;
}
}

View file

@ -0,0 +1,98 @@
package com.kabouzeid.gramophone.adapter;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.songadapter.SongAdapter;
import com.kabouzeid.gramophone.helper.SongDetailDialogHelper;
import com.kabouzeid.gramophone.loader.SongFilePathLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.tageditor.SongTagEditorActivity;
import java.io.File;
import java.util.List;
/**
* Created by karim on 24.01.15.
*/
public class PlayingQueueAdapter extends ArrayAdapter<Song> {
private Context context;
private App app;
private SongAdapter.GoToAble goToAble;
public PlayingQueueAdapter(Context context, SongAdapter.GoToAble goToAble, List<Song> playList) {
super(context, R.layout.item_playlist, playList);
this.context = context;
app = (App) context.getApplicationContext();
this.goToAble = goToAble;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Song song = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.item_playlist, parent, false);
}
final TextView title = (TextView) convertView.findViewById(R.id.song_title);
final ImageView playingIndicator = (ImageView) convertView.findViewById(R.id.playing_indicator);
final ImageView overflowButton = (ImageView) convertView.findViewById(R.id.menu);
title.setText(song.title);
if (app.getMusicPlayerRemote().getPosition() == position) {
playingIndicator.setVisibility(View.VISIBLE);
playingIndicator.setImageResource(R.drawable.ic_speaker_white_48dp);
} else {
playingIndicator.setVisibility(View.GONE);
playingIndicator.setImageBitmap(null);
}
overflowButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
PopupMenu popupMenu = new PopupMenu(context, v);
popupMenu.inflate(R.menu.menu_song);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_tag_editor:
Intent intent = new Intent(context, SongTagEditorActivity.class);
intent.putExtra(AppKeys.E_ID, song.id);
context.startActivity(intent);
return true;
case R.id.action_details:
String songFilePath = SongFilePathLoader.getSongFilePath(context, song.id);
File songFile = new File(songFilePath);
SongDetailDialogHelper.getDialog(context, songFile).show();
return true;
case R.id.action_go_to_album:
if (goToAble != null) {
goToAble.goToAlbum(song.albumId);
}
return true;
case R.id.action_go_to_artist:
if (goToAble != null) {
goToAble.goToArtist(song.artistId);
}
return true;
}
return false;
}
});
popupMenu.show();
}
});
return convertView;
}
}

View file

@ -0,0 +1,60 @@
package com.kabouzeid.gramophone.adapter;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.model.SearchEntry;
import com.kabouzeid.gramophone.ui.activities.SearchActivity;
import java.util.List;
/**
* Created by karim on 27.02.15.
*/
public class SearchAdapter extends ArrayAdapter<SearchEntry> {
public SearchAdapter(Context context, List<SearchEntry> objects) {
super(context, R.layout.item_search, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_search, parent, false);
}
SearchEntry item = getItem(position);
final TextView title = (TextView) convertView.findViewById(R.id.title);
final TextView subTitle = (TextView) convertView.findViewById(R.id.sub_title);
final ImageView imageView = (ImageView) convertView.findViewById(R.id.image);
if (item instanceof SearchActivity.LabelEntry) {
title.setTypeface(null, Typeface.BOLD);
subTitle.setVisibility(View.GONE);
imageView.setVisibility(View.GONE);
convertView.setBackgroundColor(getContext().getResources().getColor(R.color.materialmusic_default_bar_color));
} else {
title.setTypeface(null, Typeface.NORMAL);
subTitle.setVisibility(View.VISIBLE);
imageView.setVisibility(View.VISIBLE);
convertView.setBackgroundColor(Color.TRANSPARENT);
}
title.setText(item.getTitle());
subTitle.setText(item.getSubTitle());
imageView.setImageBitmap(null);
item.loadImage(getContext(), imageView);
return convertView;
}
}

View file

@ -0,0 +1,100 @@
package com.kabouzeid.gramophone.adapter.songadapter;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.helper.SongDetailDialogHelper;
import com.kabouzeid.gramophone.loader.SongFilePathLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.tageditor.SongTagEditorActivity;
import com.kabouzeid.gramophone.util.MusicUtil;
import java.io.File;
import java.util.List;
/**
* Created by karim on 27.11.14.
*/
public class SongAdapter extends ArrayAdapter<Song> {
public static final String TAG = SongAdapter.class.getSimpleName();
protected Context context;
protected GoToAble goToAble;
public SongAdapter(Context context, GoToAble goToAble, List<Song> objects) {
super(context, R.layout.item_song, objects);
this.context = context;
this.goToAble = goToAble;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Song song = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_song, parent, false);
}
TextView songTitle = (TextView) convertView.findViewById(R.id.song_title);
TextView trackNumber = (TextView) convertView.findViewById(R.id.track_number);
TextView songDuration = (TextView) convertView.findViewById(R.id.song_duration);
ImageView overflowButton = (ImageView) convertView.findViewById(R.id.menu);
overflowButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
PopupMenu popupMenu = new PopupMenu(context, v);
popupMenu.inflate(R.menu.menu_song);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_tag_editor:
Intent intent = new Intent(context, SongTagEditorActivity.class);
intent.putExtra(AppKeys.E_ID, song.id);
context.startActivity(intent);
return true;
case R.id.action_details:
String songFilePath = SongFilePathLoader.getSongFilePath(context, song.id);
File songFile = new File(songFilePath);
SongDetailDialogHelper.getDialog(context, songFile).show();
return true;
case R.id.action_go_to_album:
if (goToAble != null) {
goToAble.goToAlbum(song.albumId);
}
return true;
case R.id.action_go_to_artist:
if (goToAble != null) {
goToAble.goToArtist(song.artistId);
}
return true;
}
return false;
}
});
popupMenu.show();
}
});
songTitle.setText(song.title);
trackNumber.setText(String.valueOf(MusicUtil.getFixedTrackNumber(song.trackNumber)));
songDuration.setText(MusicUtil.getReadableDurationString(song.duration));
return convertView;
}
public static interface GoToAble {
public void goToAlbum(int albumId);
public void goToArtist(int artistId);
}
}

View file

@ -0,0 +1,88 @@
package com.kabouzeid.gramophone.adapter.songadapter;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.helper.SongDetailDialogHelper;
import com.kabouzeid.gramophone.loader.SongFilePathLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.tageditor.SongTagEditorActivity;
import com.kabouzeid.gramophone.util.ImageLoaderUtil;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.io.File;
import java.util.List;
/**
* Created by karim on 27.11.14.
*/
public class SongViewListAdapter extends SongAdapter {
public static final String TAG = SongViewListAdapter.class.getSimpleName();
public SongViewListAdapter(Context context, SongAdapter.GoToAble goToAble, List<Song> objects) {
super(context, goToAble, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Song song = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_song_view, parent, false);
}
TextView songTitle = (TextView) convertView.findViewById(R.id.song_title);
final ImageView albumArt = (ImageView) convertView.findViewById(R.id.album_art);
ImageView overflowButton = (ImageView) convertView.findViewById(R.id.menu);
overflowButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
PopupMenu popupMenu = new PopupMenu(context, v);
popupMenu.inflate(R.menu.menu_song);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_tag_editor:
Intent intent = new Intent(context, SongTagEditorActivity.class);
intent.putExtra(AppKeys.E_ID, song.id);
context.startActivity(intent);
return true;
case R.id.action_details:
String songFilePath = SongFilePathLoader.getSongFilePath(context, song.id);
File songFile = new File(songFilePath);
SongDetailDialogHelper.getDialog(context, songFile).show();
return true;
case R.id.action_go_to_album:
if (goToAble != null) {
goToAble.goToAlbum(song.albumId);
}
return true;
case R.id.action_go_to_artist:
if (goToAble != null) {
goToAble.goToArtist(song.artistId);
}
return true;
}
return false;
}
});
popupMenu.show();
}
});
songTitle.setText(song.title);
ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(song.albumId).toString(), albumArt, new ImageLoaderUtil.defaultAlbumArtOnFailed());
return convertView;
}
}

View file

@ -0,0 +1,15 @@
package com.kabouzeid.gramophone.comparator;
import com.kabouzeid.gramophone.model.Album;
import java.util.Comparator;
/**
* Created by karim on 25.11.14.
*/
public class AlbumAlphabeticComparator implements Comparator<Album> {
@Override
public int compare(Album lhs, Album rhs) {
return lhs.title.trim().compareToIgnoreCase(rhs.title.trim());
}
}

View file

@ -0,0 +1,15 @@
package com.kabouzeid.gramophone.comparator;
import com.kabouzeid.gramophone.model.Artist;
import java.util.Comparator;
/**
* Created by karim on 29.12.14.
*/
public class ArtistAlphabeticComparator implements Comparator<Artist> {
@Override
public int compare(Artist lhs, Artist rhs) {
return lhs.name.trim().compareToIgnoreCase(rhs.name.trim());
}
}

View file

@ -0,0 +1,15 @@
package com.kabouzeid.gramophone.comparator;
import com.kabouzeid.gramophone.model.Song;
import java.util.Comparator;
/**
* Created by karim on 28.12.14.
*/
public class SongAlphabeticComparator implements Comparator<Song> {
@Override
public int compare(Song lhs, Song rhs) {
return lhs.title.trim().compareToIgnoreCase(rhs.title.trim());
}
}

View file

@ -0,0 +1,24 @@
package com.kabouzeid.gramophone.comparator;
import com.kabouzeid.gramophone.model.Song;
import java.util.Comparator;
/**
* Created by karim on 25.11.14.
*/
public class SongTrackNumberComparator implements Comparator<Song> {
@Override
public int compare(Song lhs, Song rhs) {
// 0 gleich
// -1 steht über dem anderen
// 1 steht unter dem anderen
if (lhs.trackNumber == rhs.trackNumber) {
return 0;
}
if (lhs.trackNumber > rhs.trackNumber) {
return 1;
}
return -1;
}
}

View file

@ -0,0 +1,42 @@
package com.kabouzeid.gramophone.helper;
import android.content.Context;
import android.content.pm.PackageManager;
import android.util.Log;
import com.afollestad.materialdialogs.MaterialDialog;
import com.kabouzeid.gramophone.R;
/**
* Created by karim on 05.02.15.
*/
public class AboutDeveloperDialogHelper {
public static final String TAG = AboutDeveloperDialogHelper.class.getSimpleName();
public static MaterialDialog getDialog(final Context context) {
MaterialDialog dialog = new MaterialDialog.Builder(context)
.title(context.getResources().getString(R.string.app_name) + " " + getCurrentVersionName(context))
.iconRes(R.drawable.ic_launcher)
.content(context.getResources().getText(R.string.credits))
.positiveText(context.getResources().getString(R.string.ok))
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
super.onPositive(dialog);
dialog.dismiss();
}
})
.build();
return dialog;
}
private static String getCurrentVersionName(final Context context) {
String versionName = "";
try {
versionName = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable to get current app version number.", e);
}
return versionName;
}
}

View file

@ -0,0 +1,285 @@
package com.kabouzeid.gramophone.helper;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.util.Log;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.interfaces.OnMusicRemoteEventListener;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.MusicRemoteEvent;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.service.MusicService;
import com.kabouzeid.gramophone.util.InternalStorageUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by karim on 29.11.14.
*/
public class MusicPlayerRemote implements OnMusicRemoteEventListener {
private static final String TAG = MusicPlayerRemote.class.getSimpleName();
private App app;
private int position = -1;
private List<Song> playingQueue;
private List<Song> restoredOriginalQueue;
private List<OnMusicRemoteEventListener> onMusicRemoteEventListeners;
private MusicService musicService;
private Intent musicServiceIntent;
private boolean musicBound = false;
private ServiceConnection musicConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
musicService = binder.getService();
musicBound = true;
musicService.restorePreviousState(restoredOriginalQueue, playingQueue, position);
musicService.addOnMusicRemoteEventListener(MusicPlayerRemote.this);
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.SERVICE_CONNECTED);
}
@Override
public void onServiceDisconnected(ComponentName name) {
musicBound = false;
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.SERVICE_DISCONNECTED);
}
};
public MusicPlayerRemote(Context context) {
app = (App) context.getApplicationContext();
playingQueue = new ArrayList<>();
restoredOriginalQueue = new ArrayList<>();
onMusicRemoteEventListeners = new ArrayList<>();
startAndBindService();
}
private void startAndBindService() {
if (musicServiceIntent == null) {
musicServiceIntent = new Intent(app, MusicService.class);
app.bindService(musicServiceIntent, musicConnection, Context.BIND_AUTO_CREATE);
app.startService(musicServiceIntent);
}
}
public boolean playSongAt(final int position) {
if (musicBound) {
musicService.playSongAt(position);
}
return false;
}
public void pauseSong() {
if (musicBound) {
musicService.pausePlaying();
}
}
public void playNextSong() {
if (musicBound) {
musicService.playNextSong();
}
}
public void playPreviousSong() {
if (musicBound) {
musicService.back();
}
}
public void back() {
if (musicBound) {
musicService.back();
}
}
public boolean isPlaying() {
if (musicBound) {
return musicService.isPlaying();
}
return false;
}
public void resumePlaying() {
if (musicBound) {
musicService.resumePlaying();
}
}
public long getCurrentSongId() {
if (musicBound) {
return musicService.getCurrentSongId();
}
try {
return playingQueue.get(position).id;
} catch (Exception e) {
return -1;
}
}
public void openQueue(final List<Song> playingQueue, final int startPosition, final boolean startPlaying) {
this.playingQueue = playingQueue;
if (musicBound) {
musicService.openQueue(this.playingQueue, startPosition, startPlaying);
}
}
public Song getCurrentSong() {
final int position = getPosition();
if (position != -1) {
return getPlayingQueue().get(position);
}
return new Song();
}
public int getPosition() {
if (musicBound) {
position = musicService.getPosition();
}
return position;
}
private void setPosition(int position) {
this.position = position;
if (musicBound) {
musicService.setPosition(position);
}
}
public List<Song> getPlayingQueue() {
if (musicBound) {
playingQueue = musicService.getPlayingQueue();
}
return playingQueue;
}
public int getSongProgressMillis() {
if (isPlayerPrepared()) {
return musicService.getSongProgressMillis();
}
return -1;
}
public boolean isPlayerPrepared() {
if (musicBound) {
return musicService.isPlayerPrepared();
}
return false;
}
public int getSongDurationMillis() {
if (isPlayerPrepared()) {
return musicService.getSongDurationMillis();
}
return -1;
}
public boolean isMusicBound() {
return musicBound;
}
public void seekTo(int millis) {
if (musicBound) {
musicService.seekTo(millis);
}
}
public int getRepeatMode() {
if (musicBound) {
return musicService.getRepeatMode();
}
return app.getDefaultSharedPreferences().getInt(AppKeys.SP_REPEAT_MODE, 0);
}
public int getShuffleMode() {
if (musicBound) {
return musicService.getShuffleMode();
}
return app.getDefaultSharedPreferences().getInt(AppKeys.SP_SHUFFLE_MODE, 0);
}
public boolean cycleRepeatMode() {
if (musicBound) {
musicService.cycleRepeatMode();
return true;
}
return false;
}
public boolean toggleShuffleMode() {
if (musicBound) {
musicService.toggleShuffle();
return true;
}
return false;
}
public void moveSong(int from, int to) {
final int currentPosition = getPosition();
Song songToMove = getPlayingQueue().remove(from);
getPlayingQueue().add(to, songToMove);
if (from > currentPosition && to <= currentPosition) {
setPosition(getPosition() + 1);
} else if (from < currentPosition && to >= currentPosition) {
setPosition(getPosition() - 1);
} else if (from == currentPosition) {
setPosition(to);
}
}
@Override
public void onMusicRemoteEvent(MusicRemoteEvent event) {
notifyOnMusicRemoteEventListeners(event.getAction());
}
private void notifyOnMusicRemoteEventListeners(int event) {
MusicRemoteEvent musicRemoteEvent = new MusicRemoteEvent(event);
for (OnMusicRemoteEventListener listener : onMusicRemoteEventListeners) {
listener.onMusicRemoteEvent(musicRemoteEvent);
}
}
public void addOnMusicRemoteEventListener(OnMusicRemoteEventListener onMusicRemoteEventListener) {
onMusicRemoteEventListeners.add(onMusicRemoteEventListener);
}
public void removeOnMusicRemoteEventListener(OnMusicRemoteEventListener onMusicRemoteEventListener) {
onMusicRemoteEventListeners.remove(onMusicRemoteEventListener);
}
public void removeAllOnMusicRemoteEventListeners() {
onMusicRemoteEventListeners.clear();
}
@SuppressWarnings("unchecked")
public void restorePreviousState() {
try {
List restoredQueue = (ArrayList<Song>) InternalStorageUtil.readObject(app, AppKeys.IS_PLAYING_QUEUE);
List restoredOriginalQueue = (ArrayList<Song>) InternalStorageUtil.readObject(app, AppKeys.IS_ORIGINAL_PLAYING_QUEUE);
int restoredPosition = (int) InternalStorageUtil.readObject(app, AppKeys.IS_POSITION_IN_QUEUE);
if (musicBound) {
musicService.restorePreviousState(restoredOriginalQueue, restoredQueue, restoredPosition);
}
playingQueue = restoredQueue;
this.restoredOriginalQueue = restoredOriginalQueue;
position = restoredPosition;
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STATE_RESTORED);
Log.i(TAG, "restored last state");
} catch (IOException | ClassNotFoundException | ClassCastException e) {
Log.e(TAG, "error while restoring music service state", e);
playingQueue = new ArrayList<>();
position = -1;
}
}
}

View file

@ -0,0 +1,178 @@
package com.kabouzeid.gramophone.helper;
/**
* Created by karim on 27.12.14.
*/
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.support.v4.app.NotificationCompat;
import android.widget.RemoteViews;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.service.MusicService;
import com.kabouzeid.gramophone.ui.activities.MusicControllerActivity;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.nostra13.universalimageloader.core.ImageLoader;
public class PlayingNotificationHelper {
public static final String TAG = PlayingNotificationHelper.class.getSimpleName();
public static final int NOTIFICATION_ID = 1337;
private final MusicService service;
private final NotificationManager notificationManager;
private Notification notification = null;
private RemoteViews notificationLayout;
private RemoteViews notificationLayoutExpanded;
public PlayingNotificationHelper(final MusicService service) {
this.service = service;
notificationManager = (NotificationManager) service
.getSystemService(Context.NOTIFICATION_SERVICE);
}
public void buildNotification(Song song, final boolean isPlaying) {
notificationLayout = new RemoteViews(service.getPackageName(),
R.layout.notification_playing);
notificationLayoutExpanded = new RemoteViews(service.getPackageName(),
R.layout.notification_playing_expanded);
setUpCollapsedLayout(song);
setUpExpandedLayout(song);
setUpPlaybackActions(isPlaying);
setUpExpandedPlaybackActions(isPlaying);
notification = new NotificationCompat.Builder(service)
.setSmallIcon(R.drawable.notification_icon)
.setContentIntent(getOpenMusicControllerPendingIntent())
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContent(notificationLayout)
.build();
notification.bigContentView = notificationLayoutExpanded;
service.startForeground(NOTIFICATION_ID, notification);
}
private PendingIntent getOpenMusicControllerPendingIntent() {
Intent result = new Intent(service, MusicControllerActivity.class);
TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(service);
taskStackBuilder.addParentStack(MusicControllerActivity.class);
taskStackBuilder.addNextIntent(result);
return taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
}
private void setUpExpandedPlaybackActions(boolean isPlaying) {
notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_toggle_play_pause,
retrievePlaybackActions(1));
notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_next,
retrievePlaybackActions(2));
notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_prev,
retrievePlaybackActions(3));
notificationLayoutExpanded.setOnClickPendingIntent(R.id.button_quit,
retrievePlaybackActions(4));
notificationLayoutExpanded.setImageViewResource(R.id.button_toggle_play_pause,
isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp);
}
private void setUpPlaybackActions(boolean isPlaying) {
notificationLayout.setOnClickPendingIntent(R.id.button_toggle_play_pause,
retrievePlaybackActions(1));
notificationLayout.setOnClickPendingIntent(R.id.button_next,
retrievePlaybackActions(2));
notificationLayout.setOnClickPendingIntent(R.id.button_quit,
retrievePlaybackActions(4));
notificationLayout.setImageViewResource(R.id.button_toggle_play_pause,
isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp);
}
private PendingIntent retrievePlaybackActions(final int which) {
Intent action;
PendingIntent pendingIntent;
final ComponentName serviceName = new ComponentName(service, MusicService.class);
switch (which) {
case 1:
action = new Intent(MusicService.ACTION_TOGGLE_PLAYBACK);
action.setComponent(serviceName);
pendingIntent = PendingIntent.getService(service, 1, action, 0);
return pendingIntent;
case 2:
action = new Intent(MusicService.ACTION_SKIP);
action.setComponent(serviceName);
pendingIntent = PendingIntent.getService(service, 2, action, 0);
return pendingIntent;
case 3:
action = new Intent(MusicService.ACTION_REWIND);
action.setComponent(serviceName);
pendingIntent = PendingIntent.getService(service, 3, action, 0);
return pendingIntent;
case 4:
action = new Intent(MusicService.ACTION_QUIT);
action.setComponent(serviceName);
pendingIntent = PendingIntent.getService(service, 4, action, 0);
return pendingIntent;
default:
break;
}
return null;
}
private void setUpCollapsedLayout(Song song) {
loadAlbumArt(notificationLayout, MusicUtil.getAlbumArtUri(song.albumId).toString());
notificationLayout.setTextViewText(R.id.song_title, song.title);
notificationLayout.setTextViewText(R.id.song_artist, song.title);
}
private static void loadAlbumArt(RemoteViews notificationView, String albumArtUri) {
Bitmap albumArtBitmap = ImageLoader.getInstance().loadImageSync(albumArtUri);
if (albumArtBitmap == null) {
notificationView.setImageViewResource(R.id.album_art, R.drawable.default_album_art);
} else {
notificationView.setImageViewBitmap(R.id.album_art, albumArtBitmap);
}
}
private void setUpExpandedLayout(Song song) {
loadAlbumArt(notificationLayoutExpanded, MusicUtil.getAlbumArtUri(song.albumId).toString());
notificationLayoutExpanded.setTextViewText(R.id.song_title, song.title);
notificationLayoutExpanded.setTextViewText(R.id.song_artist, song.artistName);
notificationLayoutExpanded.setTextViewText(R.id.album_title, song.albumName);
}
public void killNotification() {
service.stopForeground(true);
notification = null;
}
public void updatePlayState(final boolean isPlaying) {
if (notification == null || notificationManager == null) {
return;
}
if (notificationLayout != null) {
notificationLayout.setImageViewResource(R.id.button_toggle_play_pause,
isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp);
}
if (notificationLayoutExpanded != null) {
notificationLayoutExpanded.setImageViewResource(R.id.button_toggle_play_pause,
isPlaying ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp);
}
notificationManager.notify(NOTIFICATION_ID, notification);
}
}

View file

@ -0,0 +1,65 @@
package com.kabouzeid.gramophone.helper;
import android.content.Context;
import android.view.View;
import android.widget.AdapterView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.PlayingQueueAdapter;
import com.kabouzeid.gramophone.adapter.songadapter.SongAdapter;
import com.kabouzeid.gramophone.model.Song;
import com.mobeta.android.dslv.DragSortListView;
import java.util.List;
/**
* Created by karim on 24.01.15.
*/
public class PlayingQueueDialogHelper {
public static MaterialDialog getDialog(Context context, SongAdapter.GoToAble goToAble) {
final App app = (App) context.getApplicationContext();
List<Song> playingQueue = app.getMusicPlayerRemote().getPlayingQueue();
if (playingQueue.isEmpty()) {
return null;
}
MaterialDialog dialog = new MaterialDialog.Builder(context)
.title(context.getResources().getString(R.string.label_current_playing_queue))
.customView(R.layout.dialog_playlist, false)
.positiveText(context.getResources().getString(R.string.close))
.negativeText(context.getResources().getString(R.string.save_as_playlist))
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
super.onPositive(dialog);
dialog.dismiss();
}
@Override
public void onNegative(MaterialDialog dialog) {
super.onNegative(dialog);
}
})
.build();
final DragSortListView dragSortListView = (DragSortListView) dialog.getCustomView().findViewById(R.id.dragSortListView);
final PlayingQueueAdapter playingQueueAdapter = new PlayingQueueAdapter(context, goToAble, playingQueue);
dragSortListView.setAdapter(playingQueueAdapter);
dragSortListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
app.getMusicPlayerRemote().playSongAt(position);
playingQueueAdapter.notifyDataSetChanged();
}
});
dragSortListView.setDropListener(new DragSortListView.DropListener() {
@Override
public void drop(int from, int to) {
app.getMusicPlayerRemote().moveSong(from, to);
playingQueueAdapter.notifyDataSetChanged();
}
});
return dialog;
}
}

View file

@ -0,0 +1,21 @@
package com.kabouzeid.gramophone.helper;
import com.kabouzeid.gramophone.model.Song;
import java.util.Collections;
import java.util.List;
/**
* Created by karim on 24.01.15.
*/
public class ShuffleHelper {
public static void makeShuffleList(List<Song> listToShuffle, final int current) {
if (current >= 0) {
Song song = listToShuffle.remove(current);
Collections.shuffle(listToShuffle);
listToShuffle.add(0, song);
} else {
Collections.shuffle(listToShuffle);
}
}
}

View file

@ -0,0 +1,84 @@
package com.kabouzeid.gramophone.helper;
import android.content.Context;
import android.text.Html;
import android.text.Spanned;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.Util;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.AudioHeader;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.TagException;
import java.io.File;
import java.io.IOException;
/**
* Created by karim on 19.01.15.
*/
public class SongDetailDialogHelper {
public static final String TAG = SongDetailDialogHelper.class.getSimpleName();
public static MaterialDialog getDialog(final Context context, final File songFile) {
MaterialDialog dialog = new MaterialDialog.Builder(context)
.customView(R.layout.dialog_file_details, true)
.title(context.getResources().getString(R.string.label_details))
.positiveText(context.getResources().getString(R.string.ok))
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
dialog.dismiss();
}
})
.build();
View dialogView = dialog.getCustomView();
final TextView fileName = (TextView) dialogView.findViewById(R.id.file_name);
final TextView filePath = (TextView) dialogView.findViewById(R.id.file_path);
final TextView fileSize = (TextView) dialogView.findViewById(R.id.file_size);
final TextView fileFormat = (TextView) dialogView.findViewById(R.id.file_format);
final TextView trackLength = (TextView) dialogView.findViewById(R.id.track_length);
final TextView bitRate = (TextView) dialogView.findViewById(R.id.bitrate);
final TextView samplingRate = (TextView) dialogView.findViewById(R.id.sampling_rate);
fileName.setText(makeTextWithTitle(context, R.string.label_file_name, "-"));
filePath.setText(makeTextWithTitle(context, R.string.label_file_path, "-"));
fileSize.setText(makeTextWithTitle(context, R.string.label_file_size, "-"));
fileFormat.setText(makeTextWithTitle(context, R.string.label_file_format, "-"));
trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, "-"));
bitRate.setText(makeTextWithTitle(context, R.string.label_bit_rate, "-"));
samplingRate.setText(makeTextWithTitle(context, R.string.label_sampling_rate, "-"));
try {
if (songFile != null && songFile.exists()) {
AudioFile audioFile = AudioFileIO.read(songFile);
AudioHeader audioHeader = audioFile.getAudioHeader();
fileName.setText(makeTextWithTitle(context, R.string.label_file_name, songFile.getName()));
filePath.setText(makeTextWithTitle(context, R.string.label_file_path, songFile.getAbsolutePath()));
fileSize.setText(makeTextWithTitle(context, R.string.label_file_size, Util.getFileSizeString(songFile.length())));
fileFormat.setText(makeTextWithTitle(context, R.string.label_file_format, audioHeader.getFormat()));
trackLength.setText(makeTextWithTitle(context, R.string.label_track_length, MusicUtil.getReadableDurationString(audioHeader.getTrackLength() * 1000)));
bitRate.setText(makeTextWithTitle(context, R.string.label_bit_rate, audioHeader.getBitRate() + " kb/s"));
samplingRate.setText(makeTextWithTitle(context, R.string.label_sampling_rate, audioHeader.getSampleRate() + " Hz"));
}
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
Log.e(TAG, "error while reading the song file", e);
}
return dialog;
}
private static Spanned makeTextWithTitle(Context context, int titleResId, String text) {
return Html.fromHtml("<b>" + context.getResources().getString(titleResId) + ": " + "</b>" + text);
}
}

View file

@ -0,0 +1,10 @@
package com.kabouzeid.gramophone.interfaces;
/**
* Created by karim on 29.12.14.
*/
public interface KabSearchAbleFragment {
public void search(String query);
public void returnToNonSearch();
}

View file

@ -0,0 +1,12 @@
package com.kabouzeid.gramophone.interfaces;
/**
* Created by karim on 23.12.14.
*/
public interface KabViewsDisableAble {
public void enableViews();
public void disableViews();
public boolean areViewsEnabled();
}

View file

@ -0,0 +1,10 @@
package com.kabouzeid.gramophone.interfaces;
import com.kabouzeid.gramophone.model.MusicRemoteEvent;
/**
* Created by karim on 19.12.14.
*/
public interface OnMusicRemoteEventListener {
public void onMusicRemoteEvent(MusicRemoteEvent event);
}

View file

@ -0,0 +1,9 @@
package com.kabouzeid.gramophone.lastfm;
/**
* Created by karim on 15.01.15.
*/
public class LastFMUtil {
public static String BASE_URL = "ws.audioscrobbler.com";
public static String API_KEY = "bd9c6ea4d55ec9ed3af7d276e5ece304";
}

View file

@ -0,0 +1,110 @@
package com.kabouzeid.gramophone.lastfm.album;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.View;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.provider.AlbumJSONStore;
import com.kabouzeid.gramophone.util.Util;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.core.process.BitmapProcessor;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by karim on 01.01.15.
*/
public class LastFMAlbumImageLoader {
public static final String TAG = LastFMAlbumImageLoader.class.getSimpleName();
public static void loadAlbumImage(Context context, String queryAlbum, String queryArtist, AlbumImageLoaderCallback callback) {
if (queryAlbum != null) {
String albumJSON = AlbumJSONStore.getInstance(context).getAlbumJSON(queryAlbum + queryArtist);
if (albumJSON != null) {
Log.i(TAG, queryAlbum + " by " + queryArtist + " is in cache.");
try {
loadAlbumImageFromJSON(new JSONObject(albumJSON), callback);
} catch (JSONException e) {
Log.e(TAG, "Error while parsing string from cache to JSONObject", e);
}
} else {
Log.i(TAG, queryAlbum + " is not in cache.");
downloadAlbumImage(context, queryAlbum, queryArtist, callback);
}
}
}
private static void loadAlbumImageFromJSON(JSONObject jsonObject, final AlbumImageLoaderCallback callback) {
Log.i(TAG, "Applying album art...");
String url = LastFMAlbumInfoUtil.getAlbumImageUrlFromJSON(jsonObject);
if (!url.trim().equals("")) {
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(false)
.postProcessor(new BitmapProcessor() {
@Override
public Bitmap process(Bitmap bmp) {
return Util.getAlbumArtScaledBitmap(bmp, true);
}
})
.build();
ImageLoader.getInstance().loadImage(url, options, new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
callback.onAlbumImageLoaded(null, null);
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
callback.onAlbumImageLoaded(loadedImage, imageUri);
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
callback.onAlbumImageLoaded(null, null);
}
});
} else {
callback.onAlbumImageLoaded(null, null);
}
}
private static void downloadAlbumImage(final Context context, final String album, final String artist, final AlbumImageLoaderCallback callback) {
Log.i(TAG, "Downloading details for " + album);
App app = (App) context.getApplicationContext();
String albumUrl = LastFMAlbumInfoUtil.getAlbumUrl(album, artist);
JsonObjectRequest albumInfoJSONRequest = new JsonObjectRequest(0, albumUrl, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.i(TAG, "Download was successful!");
LastFMAlbumInfoUtil.saveAlbumJSONDataToCacheAndDisk(context, album, artist, response);
loadAlbumImageFromJSON(response, callback);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Download failed!", error);
callback.onAlbumImageLoaded(null, null);
}
});
app.addToVolleyRequestQueue(albumInfoJSONRequest);
}
public static interface AlbumImageLoaderCallback {
public void onAlbumImageLoaded(Bitmap albumImage, String uri);
}
}

View file

@ -0,0 +1,87 @@
package com.kabouzeid.gramophone.lastfm.album;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.kabouzeid.gramophone.lastfm.LastFMUtil;
import com.kabouzeid.gramophone.provider.AlbumJSONStore;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by karim on 24.12.14.
*/
public class LastFMAlbumInfoUtil {
public static final String TAG = LastFMAlbumInfoUtil.class.getSimpleName();
private static String AUTO_CORRECT = "1";
public static String getAlbumUrl(String album, String artist) {
if (album != null) {
Uri.Builder builder = new Uri.Builder();
builder.scheme("http")
.authority(LastFMUtil.BASE_URL)
.appendPath("2.0")
.appendQueryParameter("method", "album.getinfo")
.appendQueryParameter("album", album)
.appendQueryParameter("artist", artist)
//.appendQueryParameter("lang", "de")
.appendQueryParameter("autocorrect", AUTO_CORRECT)
.appendQueryParameter("api_key", LastFMUtil.API_KEY)
.appendQueryParameter("format", "json");
return builder.build().toString();
}
return "";
}
public static String getAlbumNameFromJSON(JSONObject rootJSON) {
try {
return rootJSON.getJSONObject("album").getString("name");
} catch (JSONException e) {
//Log.e(TAG, "Error while getting album name from JSON parameter!", e);
return "";
}
}
public static String getAlbumThumbnailUrlFromJSON(JSONObject rootJSON) {
try {
JSONArray images = getAlbumImageArrayFromJSON(rootJSON);
if (images.length() > 2) {
return images.getJSONObject(2).getString("#text");
} else if (images.length() > 1) {
return images.getJSONObject(1).getString("#text");
}
return images.getJSONObject(0).getString("#text");
} catch (JSONException | NullPointerException e) {
//Log.e(TAG, "Error while getting album thumbnail image from JSON parameter!", e);
return "";
}
}
public static JSONArray getAlbumImageArrayFromJSON(JSONObject rootJSON) {
try {
return rootJSON.getJSONObject("album").getJSONArray("image");
} catch (JSONException e) {
//Log.e(TAG, "Error while getting album image array from JSON parameter!", e);
return null;
}
}
public static String getAlbumImageUrlFromJSON(JSONObject rootJSON) {
try {
JSONArray images = getAlbumImageArrayFromJSON(rootJSON);
return images.getJSONObject(images.length() - 1).getString("#text");
} catch (JSONException | NullPointerException e) {
//Log.e(TAG, "Error while getting album image from JSON parameter!", e);
return "";
}
}
public static void saveAlbumJSONDataToCacheAndDisk(Context context, String album, String artist, JSONObject jsonObject) {
Log.i(TAG, "Saving new JSON album data for " + album + "...");
AlbumJSONStore.getInstance(context).addAlbumJSON(album + artist, jsonObject.toString());
}
}

View file

@ -0,0 +1,65 @@
package com.kabouzeid.gramophone.lastfm.artist;
import android.content.Context;
import android.util.Log;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.provider.ArtistJSONStore;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by karim on 01.01.15.
*/
public class LastFMArtistBiographyLoader {
public static final String TAG = LastFMArtistBiographyLoader.class.getSimpleName();
public static void loadArtistBio(Context context, String queryArtist, ArtistBioLoaderCallback callback) {
if (queryArtist != null) {
String artistJSON = ArtistJSONStore.getInstance(context).getArtistJSON(queryArtist);
if (artistJSON != null) {
Log.i(TAG, queryArtist + " is in cache.");
try {
JSONObject json = new JSONObject(artistJSON);
String bio = LastFMArtistInfoUtil.getArtistBiographyFromJSON(json);
callback.onArtistBioLoaded(bio);
} catch (JSONException e) {
Log.e(TAG, "Error while parsing bio from cache to JSONObject", e);
}
} else {
Log.i(TAG, queryArtist + " is not in cache.");
downloadArtistBio(context, queryArtist, callback);
}
}
}
private static void downloadArtistBio(final Context context, final String artist, final ArtistBioLoaderCallback callback) {
Log.i(TAG, "Downloading details for " + artist);
App app = (App) context.getApplicationContext();
String artistUrl = LastFMArtistInfoUtil.getArtistUrl(artist);
JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.i(TAG, "Download was successful!");
LastFMArtistInfoUtil.saveArtistJSONDataToCacheAndDisk(context, artist, response);
String bio = LastFMArtistInfoUtil.getArtistBiographyFromJSON(response);
callback.onArtistBioLoaded(bio);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Download failed!", error);
callback.onArtistBioLoaded("");
}
});
app.addToVolleyRequestQueue(artistInfoJSONRequest);
}
public static interface ArtistBioLoaderCallback {
public void onArtistBioLoaded(String bio);
}
}

View file

@ -0,0 +1,107 @@
package com.kabouzeid.gramophone.lastfm.artist;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.View;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.provider.ArtistJSONStore;
import com.kabouzeid.gramophone.util.ImageLoaderUtil;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by karim on 01.01.15.
*/
public class LastFMArtistImageLoader {
public static final String TAG = LastFMArtistImageLoader.class.getSimpleName();
@Deprecated
public static void loadArtistImage(Context context, String queryArtist, ArtistImageLoaderCallback callback) {
loadArtistImage(context, queryArtist, false, callback);
}
public static void loadArtistImage(Context context, String queryArtist, boolean forceDownload, ArtistImageLoaderCallback callback) {
if (queryArtist != null) {
String artistJSON = ArtistJSONStore.getInstance(context).getArtistJSON(queryArtist);
if (artistJSON != null && !forceDownload) {
Log.i(TAG, queryArtist + " is in cache.");
try {
loadArtistImageFromJSON(new JSONObject(artistJSON), callback);
} catch (JSONException e) {
Log.e(TAG, "Error while parsing string from cache to JSONObject", e);
}
} else {
if(forceDownload){
Log.i(TAG, queryArtist + " force re-download");
} else {
Log.i(TAG, queryArtist + " is not in cache.");
}
downloadArtistJSONAndStartImageDownload(context, queryArtist, callback);
}
}
}
private static void loadArtistImageFromJSON(JSONObject jsonObject, final ArtistImageLoaderCallback callback) {
Log.i(TAG, "Applying artist art...");
String url = LastFMArtistInfoUtil.getArtistImageUrlFromJSON(jsonObject);
if (!url.trim().equals("")) {
ImageLoader.getInstance().loadImage(url, ImageLoaderUtil.getCacheOnDiskOptions(), new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
callback.onArtistImageLoaded(null);
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
callback.onArtistImageLoaded(loadedImage);
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
callback.onArtistImageLoaded(null);
}
});
} else {
callback.onArtistImageLoaded(null);
}
}
private static void downloadArtistJSONAndStartImageDownload(final Context context, final String artist, final ArtistImageLoaderCallback callback) {
Log.i(TAG, "Downloading details for " + artist);
App app = (App) context.getApplicationContext();
String artistUrl = LastFMArtistInfoUtil.getArtistUrl(artist);
JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.i(TAG, "Download was successful!");
LastFMArtistInfoUtil.saveArtistJSONDataToCacheAndDisk(context, artist, response);
loadArtistImageFromJSON(response, callback);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Download failed!", error);
callback.onArtistImageLoaded(null);
}
});
app.addToVolleyRequestQueue(artistInfoJSONRequest);
}
public static interface ArtistImageLoaderCallback {
public void onArtistImageLoaded(Bitmap artistImage);
}
}

View file

@ -0,0 +1,96 @@
package com.kabouzeid.gramophone.lastfm.artist;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.kabouzeid.gramophone.lastfm.LastFMUtil;
import com.kabouzeid.gramophone.provider.ArtistJSONStore;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by karim on 24.12.14.
*/
public class LastFMArtistInfoUtil {
public static final String TAG = LastFMArtistInfoUtil.class.getSimpleName();
private static String AUTO_CORRECT = "1";
public static String getArtistUrl(String artist) {
if (artist != null) {
Uri.Builder builder = new Uri.Builder();
builder.scheme("http")
.authority(LastFMUtil.BASE_URL)
.appendPath("2.0")
.appendQueryParameter("method", "artist.getinfo")
.appendQueryParameter("artist", artist)
//.appendQueryParameter("lang", "de")
.appendQueryParameter("autocorrect", AUTO_CORRECT)
.appendQueryParameter("api_key", LastFMUtil.API_KEY)
.appendQueryParameter("format", "json");
return builder.build().toString();
}
return "";
}
public static String getArtistNameFromJSON(JSONObject rootJSON) {
try {
return rootJSON.getJSONObject("artist").getString("name");
} catch (JSONException e) {
//Log.e(TAG, "Error while getting artist name from JSON parameter!", e);
return "";
}
}
public static String getArtistThumbnailUrlFromJSON(JSONObject rootJSON) {
try {
JSONArray images = getArtistImageArrayFromJSON(rootJSON);
if (images.length() > 2) {
return images.getJSONObject(2).getString("#text");
} else if (images.length() > 1) {
return images.getJSONObject(1).getString("#text");
}
return images.getJSONObject(0).getString("#text");
} catch (JSONException | NullPointerException e) {
//Log.e(TAG, "Error while getting artist thumbnail image from JSON parameter!", e);
return "";
}
}
public static JSONArray getArtistImageArrayFromJSON(JSONObject rootJSON) {
try {
return rootJSON.getJSONObject("artist").getJSONArray("image");
} catch (JSONException e) {
//Log.e(TAG, "Error while getting artist image array from JSON parameter!", e);
return null;
}
}
public static String getArtistImageUrlFromJSON(JSONObject rootJSON) {
try {
JSONArray images = getArtistImageArrayFromJSON(rootJSON);
return images.getJSONObject(images.length() - 1).getString("#text");
} catch (JSONException | NullPointerException e) {
//Log.e(TAG, "Error while getting artist image from JSON parameter!", e);
return "";
}
}
public static String getArtistBiographyFromJSON(JSONObject rootJSON) {
try {
return rootJSON.getJSONObject("artist").getJSONObject("bio").getString("content");
} catch (JSONException e) {
//Log.e(TAG, "Error while getting artist biografie from JSON parameter!", e);
return "";
}
}
public static void saveArtistJSONDataToCacheAndDisk(Context context, String artist, JSONObject jsonObject) {
Log.i(TAG, "Saving new JSON artist data for " + artist + "...");
ArtistJSONStore.getInstance(context).removeItem(artist);
ArtistJSONStore.getInstance(context).addArtistJSON(artist, jsonObject.toString());
}
}

View file

@ -0,0 +1,107 @@
package com.kabouzeid.gramophone.lastfm.artist;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.View;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.provider.ArtistJSONStore;
import com.kabouzeid.gramophone.util.ImageLoaderUtil;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Created by karim on 01.01.15.
*/
public class LastFMArtistThumbnailLoader {
public static final String TAG = LastFMArtistThumbnailLoader.class.getSimpleName();
@Deprecated
public static void loadArtistThumbnail(Context context, String queryArtist, ArtistThumbnailLoaderCallback callback) {
loadArtistThumbnail(context, queryArtist, false, callback);
}
public static void loadArtistThumbnail(Context context, String queryArtist, boolean forceDownload, ArtistThumbnailLoaderCallback callback) {
if (queryArtist != null) {
String artistJSON = ArtistJSONStore.getInstance(context).getArtistJSON(queryArtist);
if (artistJSON != null && !forceDownload) {
Log.i(TAG, queryArtist + " is in cache.");
try {
loadArtistThumbnailFromJSON(new JSONObject(artistJSON), callback);
} catch (JSONException e) {
Log.e(TAG, "Error while parsing string from cache to JSONObject", e);
}
} else {
if(forceDownload){
Log.i(TAG, queryArtist + " force re-download");
} else {
Log.i(TAG, queryArtist + " is not in cache.");
}
downloadArtistThumbnail(context, queryArtist, callback);
}
}
}
private static void loadArtistThumbnailFromJSON(JSONObject jsonObject, final ArtistThumbnailLoaderCallback callback) {
Log.i(TAG, "Applying artist thumbnail...");
String url = LastFMArtistInfoUtil.getArtistThumbnailUrlFromJSON(jsonObject);
if (!url.trim().equals("")) {
ImageLoader.getInstance().loadImage(url, ImageLoaderUtil.getCacheOnDiskOptions(), new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
callback.onArtistThumbnailLoaded(null);
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
callback.onArtistThumbnailLoaded(loadedImage);
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
callback.onArtistThumbnailLoaded(null);
}
});
} else {
callback.onArtistThumbnailLoaded(null);
}
}
private static void downloadArtistThumbnail(final Context context, final String artist, final ArtistThumbnailLoaderCallback callback) {
Log.i(TAG, "Downloading details for " + artist);
App app = (App) context.getApplicationContext();
String artistUrl = LastFMArtistInfoUtil.getArtistUrl(artist);
JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Log.i(TAG, "Download was successful!");
LastFMArtistInfoUtil.saveArtistJSONDataToCacheAndDisk(context, artist, response);
loadArtistThumbnailFromJSON(response, callback);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(TAG, "Download failed!", error);
callback.onArtistThumbnailLoaded(null);
}
});
app.addToVolleyRequestQueue(artistInfoJSONRequest);
}
public static interface ArtistThumbnailLoaderCallback {
public void onArtistThumbnailLoaded(Bitmap thumbnail);
}
}

View file

@ -0,0 +1,104 @@
package com.kabouzeid.gramophone.loader;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import com.kabouzeid.gramophone.model.Album;
import java.util.ArrayList;
import java.util.List;
/**
* Created by karim on 29.12.14.
*/
public class AlbumLoader {
public static List<Album> getAllAlbums(Context context) {
Cursor cursor = makeAlbumCursor(context);
List<Album> albums = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
final int id = cursor.getInt(0);
final String albumName = cursor.getString(1);
final String artist = cursor.getString(2);
final int artistId = cursor.getInt(3);
final int songCount = cursor.getInt(4);
final int year = cursor.getInt(5);
final Album album = new Album(id, albumName, artist, artistId, songCount, year);
albums.add(album);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return albums;
}
public static final Cursor makeAlbumCursor(final Context context) {
return makeAlbumCursor(context, null);
}
public static final Cursor makeAlbumCursor(final Context context, String selection) {
return context.getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */
BaseColumns._ID,
/* 1 */
MediaStore.Audio.AlbumColumns.ALBUM,
/* 2 */
MediaStore.Audio.AlbumColumns.ARTIST,
/* 3 */
MediaStore.Audio.Media.ARTIST_ID,
/* 4 */
MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS,
/* 5 */
MediaStore.Audio.AlbumColumns.FIRST_YEAR
}, selection, null, null);
}
public static Album getAlbum(Context context, int albumId) {
Cursor cursor = makeAlbumCursor(context, BaseColumns._ID + "=" + albumId);
Album album = new Album();
if (cursor != null && cursor.moveToFirst()) {
final int id = cursor.getInt(0);
final String albumName = cursor.getString(1);
final String artist = cursor.getString(2);
final int artistId = cursor.getInt(3);
final int songCount = cursor.getInt(4);
final int year = cursor.getInt(5);
album = new Album(id, albumName, artist, artistId, songCount, year);
}
if (cursor != null) {
cursor.close();
}
return album;
}
public static List<Album> getAlbums(Context context, String query) {
Cursor cursor = makeAlbumCursor(context, MediaStore.Audio.AlbumColumns.ALBUM + " LIKE '%" + query + "%'");
List<Album> albums = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
final String albumName = cursor.getString(1);
final int id = cursor.getInt(0);
final String artist = cursor.getString(2);
final int artistId = cursor.getInt(3);
final int songCount = cursor.getInt(4);
final int year = cursor.getInt(5);
final Album album = new Album(id, albumName, artist, artistId, songCount, year);
albums.add(album);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return albums;
}
}

View file

@ -0,0 +1,73 @@
package com.kabouzeid.gramophone.loader;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import com.kabouzeid.gramophone.model.Song;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Created by karim on 29.12.14.
*/
public class AlbumSongLoader {
public static List<Song> getAlbumSongList(final Context context, final int albumId, Comparator<Song> comparator) {
List<Song> songs = getAlbumSongList(context, albumId);
Collections.sort(songs, comparator);
return songs;
}
public static List<Song> getAlbumSongList(final Context context, final int albumId) {
Cursor cursor = makeAlbumSongCursor(context, albumId);
List<Song> songs = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
final int id = cursor.getInt(0);
final String songName = cursor.getString(1);
final String artist = cursor.getString(2);
final String album = cursor.getString(3);
final long duration = cursor.getLong(4);
final int trackNumber = cursor.getInt(5);
final int artistId = cursor.getInt(6);
final Song song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber);
songs.add(song);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return songs;
}
public static final Cursor makeAlbumSongCursor(final Context context, final int albumId) {
final StringBuilder selection = new StringBuilder();
selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1");
selection.append(" AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''");
selection.append(" AND " + MediaStore.Audio.AudioColumns.ALBUM_ID + "=" + albumId);
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */
BaseColumns._ID,
/* 1 */
MediaStore.Audio.AudioColumns.TITLE,
/* 2 */
MediaStore.Audio.AudioColumns.ARTIST,
/* 3 */
MediaStore.Audio.AudioColumns.ALBUM,
/* 4 */
MediaStore.Audio.AudioColumns.DURATION,
/* 5 */
MediaStore.Audio.AudioColumns.TRACK,
/* 6 */
MediaStore.Audio.AudioColumns.ARTIST_ID
}, selection.toString(), null, null);
}
}

View file

@ -0,0 +1,55 @@
package com.kabouzeid.gramophone.loader;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import com.kabouzeid.gramophone.model.Album;
import java.util.ArrayList;
import java.util.List;
/**
* Created by karim on 04.01.15.
*/
public class ArtistAlbumLoader {
public static List<Album> getArtistAlbumList(final Context context, final int artistId) {
Cursor cursor = makeArtistAlbumCursor(context, artistId);
List<Album> albums = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
final int id = cursor.getInt(0);
final String albumName = cursor.getString(1);
final String artist = cursor.getString(2);
final int songCount = cursor.getInt(3);
final int year = cursor.getInt(4);
final Album album = new Album(id, albumName, artist, artistId, songCount, year);
albums.add(album);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return albums;
}
public static Cursor makeArtistAlbumCursor(final Context context, final int artistId) {
if (artistId == -1) return null;
return context.getContentResolver().query(
MediaStore.Audio.Artists.Albums.getContentUri("external", artistId), new String[]{
/* 0 */
BaseColumns._ID,
/* 1 */
MediaStore.Audio.AlbumColumns.ALBUM,
/* 2 */
MediaStore.Audio.AlbumColumns.ARTIST,
/* 3 */
MediaStore.Audio.AlbumColumns.NUMBER_OF_SONGS,
/* 4 */
MediaStore.Audio.AlbumColumns.FIRST_YEAR
}, null, null, null);
}
}

View file

@ -0,0 +1,95 @@
package com.kabouzeid.gramophone.loader;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import com.kabouzeid.gramophone.model.Artist;
import java.util.ArrayList;
import java.util.List;
/**
* Created by karim on 29.12.14.
*/
public class ArtistLoader {
public static List<Artist> getAllArtists(Context context) {
Cursor cursor = makeArtistCursor(context);
List<Artist> artists = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
final int id = cursor.getInt(0);
final String artistName = cursor.getString(1);
final int albumCount = cursor.getInt(2);
final int songCount = cursor.getInt(3);
final Artist artist = new Artist(id, artistName, albumCount, songCount);
artists.add(artist);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return artists;
}
public static final Cursor makeArtistCursor(final Context context) {
return makeArtistCursor(context, null);
}
public static final Cursor makeArtistCursor(final Context context, String selection) {
return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */
BaseColumns._ID,
/* 1 */
MediaStore.Audio.ArtistColumns.ARTIST,
/* 2 */
MediaStore.Audio.ArtistColumns.NUMBER_OF_ALBUMS,
/* 3 */
MediaStore.Audio.ArtistColumns.NUMBER_OF_TRACKS
}, selection, null, null);
}
public static Artist getArtist(Context context, int artistId) {
Cursor cursor = makeArtistCursor(context, BaseColumns._ID + "=" + artistId);
Artist artist = new Artist();
if (cursor != null && cursor.moveToFirst()) {
final int id = cursor.getInt(0);
final String artistName = cursor.getString(1);
final int albumCount = cursor.getInt(2);
final int songCount = cursor.getInt(3);
artist = new Artist(id, artistName, albumCount, songCount);
}
if (cursor != null) {
cursor.close();
}
return artist;
}
public static List<Artist> getArtists(Context context, String query) {
Cursor cursor = makeArtistCursor(context, MediaStore.Audio.ArtistColumns.ARTIST + " LIKE '%" + query + "%'");
List<Artist> artists = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
final String artistName = cursor.getString(1);
final int id = cursor.getInt(0);
final int albumCount = cursor.getInt(2);
final int songCount = cursor.getInt(3);
final Artist artist = new Artist(id, artistName, albumCount, songCount);
artists.add(artist);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return artists;
}
}

View file

@ -0,0 +1,64 @@
package com.kabouzeid.gramophone.loader;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import com.kabouzeid.gramophone.model.Song;
import java.util.ArrayList;
import java.util.List;
/**
* Created by karim on 01.01.15.
*/
public class ArtistSongLoader {
public static List<Song> getArtistSongList(final Context context, final int artistId) {
Cursor cursor = makeArtistSongCursor(context, artistId);
List<Song> songs = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
final int id = cursor.getInt(0);
final String songName = cursor.getString(1);
final String artist = cursor.getString(2);
final String album = cursor.getString(3);
final long duration = cursor.getLong(4);
final int trackNumber = cursor.getInt(5);
final int albumId = cursor.getInt(6);
final Song song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber);
songs.add(song);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return songs;
}
public static Cursor makeArtistSongCursor(final Context context, final int artistId) {
final StringBuilder selection = new StringBuilder();
selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1");
selection.append(" AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''");
selection.append(" AND " + MediaStore.Audio.AudioColumns.ARTIST_ID + "=" + artistId);
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */
BaseColumns._ID,
/* 1 */
MediaStore.Audio.AudioColumns.TITLE,
/* 2 */
MediaStore.Audio.AudioColumns.ARTIST,
/* 3 */
MediaStore.Audio.AudioColumns.ALBUM,
/* 4 */
MediaStore.Audio.AudioColumns.DURATION,
/* 5 */
MediaStore.Audio.AudioColumns.TRACK,
/* 6 */
MediaStore.Audio.AudioColumns.ALBUM_ID
}, selection.toString(), null, null);
}
}

View file

@ -0,0 +1,60 @@
package com.kabouzeid.gramophone.loader;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import java.util.ArrayList;
import java.util.List;
/**
* Created by karim on 11.01.15.
*/
public class SongFilePathLoader {
public static final String TAG = SongFilePathLoader.class.getSimpleName();
public static List<String> getSongFilePaths(Context context, int[] queryIds) {
StringBuilder selectionBuilder = new StringBuilder();
selectionBuilder.append(BaseColumns._ID).append(" IN(");
for (int i = 0; i < queryIds.length; i++) {
selectionBuilder.append(queryIds[i]);
if (i < queryIds.length - 1) {
selectionBuilder.append(",");
}
}
selectionBuilder.append(")");
Cursor cursor = makeSongFilePathCursor(context, selectionBuilder.toString());
List<String> songFiles = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
songFiles.add(cursor.getString(0));
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return songFiles;
}
public static String getSongFilePath(Context context, int queryId){
try {
return getSongFilePaths(context, new int[]{queryId}).get(0);
} catch (Exception e){
return "";
}
}
public static final Cursor makeSongFilePathCursor(final Context context) {
return makeSongFilePathCursor(context, null);
}
public static final Cursor makeSongFilePathCursor(final Context context, String selection) {
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */
MediaStore.Audio.AudioColumns.DATA,
}, selection, null, null);
}
}

View file

@ -0,0 +1,112 @@
package com.kabouzeid.gramophone.loader;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import com.kabouzeid.gramophone.model.Song;
import java.util.ArrayList;
import java.util.List;
/**
* Created by karim on 29.12.14.
*/
public class SongLoader {
public static List<Song> getAllSongs(Context context) {
Cursor cursor = makeSongCursor(context);
List<Song> songs = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
final int id = cursor.getInt(0);
final String songName = cursor.getString(1);
final String artist = cursor.getString(2);
final String album = cursor.getString(3);
final long duration = cursor.getLong(4);
final int trackNumber = cursor.getInt(5);
final int artistId = cursor.getInt(6);
final int albumId = cursor.getInt(7);
final Song song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber);
songs.add(song);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return songs;
}
public static final Cursor makeSongCursor(final Context context) {
return makeSongCursor(context, (MediaStore.Audio.AudioColumns.IS_MUSIC + "=1"));
}
public static final Cursor makeSongCursor(final Context context, final String selection) {
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */
BaseColumns._ID,
/* 1 */
MediaStore.Audio.AudioColumns.TITLE,
/* 2 */
MediaStore.Audio.AudioColumns.ARTIST,
/* 3 */
MediaStore.Audio.AudioColumns.ALBUM,
/* 4 */
MediaStore.Audio.AudioColumns.DURATION,
/* 5 */
MediaStore.Audio.AudioColumns.TRACK,
/* 6 */
MediaStore.Audio.AudioColumns.ARTIST_ID,
/* 7 */
MediaStore.Audio.AudioColumns.ALBUM_ID
}, selection, null, null);
}
public static List<Song> getSongs(Context context, String query) {
Cursor cursor = makeSongCursor(context, MediaStore.Audio.AudioColumns.TITLE + " LIKE '%" + query + "%'");
List<Song> songs = new ArrayList<>();
if (cursor != null && cursor.moveToFirst()) {
do {
final String songName = cursor.getString(1);
final int id = cursor.getInt(0);
final String artist = cursor.getString(2);
final String album = cursor.getString(3);
final long duration = cursor.getLong(4);
final int trackNumber = cursor.getInt(5);
final int artistId = cursor.getInt(6);
final int albumId = cursor.getInt(7);
final Song song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber);
songs.add(song);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return songs;
}
public static Song getSong(Context context, int queryId) {
Cursor cursor = makeSongCursor(context, MediaStore.Audio.AudioColumns._ID + "=" + queryId);
Song song = null;
if (cursor != null && cursor.moveToFirst()) {
final int id = cursor.getInt(0);
final String songName = cursor.getString(1);
final String artist = cursor.getString(2);
final String album = cursor.getString(3);
final long duration = cursor.getLong(4);
final int trackNumber = cursor.getInt(5);
final int artistId = cursor.getInt(6);
final int albumId = cursor.getInt(7);
song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber);
}
if (cursor != null) {
cursor.close();
}
return song;
}
}

View file

@ -0,0 +1,26 @@
package com.kabouzeid.gramophone.misc;
/**
* Created by karim on 22.12.14.
*/
public final class AppKeys {
public static final String CL_CURRENT_ACTIVITY = "Current activity";
public static final String SP_THEME = "com.kabouzeid.gramophone.THEME";
public static final String SP_VIEWPAGER_ITEM_POSITION = "com.kabouzeid.gramophone.NAVIGATION_DRAWER_ITEM_POSITION";
public static final String SP_USER_LEARNED_DRAWER = "com.kabouzeid.gramophone.NAVIGATION_DRAWER_LEARNED";
public static final String SP_ONLY_ON_WIFI = "com.kabouzeid.gramophone.ONLY_ON_WIFI";
public static final String SP_SHUFFLE_MODE = "com.kabouzeid.gramophone.SHUFFLE_MODE";
public static final String SP_REPEAT_MODE = "com.kabouzeid.gramophone.REPEAT_MODE";
public static final String IS_ORIGINAL_PLAYING_QUEUE = "com.kabouzeid.gramophone.ORIGINAL_PLAYING_QUEUE";
public static final String IS_PLAYING_QUEUE = "com.kabouzeid.gramophone.PLAYING_QUEUE";
public static final String IS_POSITION_IN_QUEUE = "com.kabouzeid.gramophone.POSITION_IN_QUEUE";
public static final String IS_ARTIST_JSON_INFO_CACHE = "com.kabouzeid.gramophone.ARTIST_JSON_INFO_CACHE";
public static final String E_ALBUM = "com.kabouzeid.gramophone.ALBUM";
public static final String E_ARTIST = "com.kabouzeid.gramophone.ARTIST";
public static final String E_SONG = "com.kabouzeid.gramophone.SONG";
public static final String E_TAG_EDIT_MODE = "com.kabouzeid.gramophone.TAG_EDIT_MODE";
public static final String E_ID = "com.kabouzeid.gramophone.ID";
}

View file

@ -0,0 +1,26 @@
package com.kabouzeid.gramophone.misc;
/**
* Created by karim on 20.12.14.
*/
public class SmallAnimatorListener implements com.nineoldandroids.animation.Animator.AnimatorListener {
@Override
public void onAnimationStart(com.nineoldandroids.animation.Animator animation) {
}
@Override
public void onAnimationEnd(com.nineoldandroids.animation.Animator animation) {
}
@Override
public void onAnimationCancel(com.nineoldandroids.animation.Animator animation) {
}
@Override
public void onAnimationRepeat(com.nineoldandroids.animation.Animator animation) {
}
}

View file

@ -0,0 +1,24 @@
package com.kabouzeid.gramophone.misc;
import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks;
import com.github.ksoichiro.android.observablescrollview.ScrollState;
/**
* Created by karim on 20.12.14.
*/
public class SmallObservableScrollViewCallbacks implements ObservableScrollViewCallbacks {
@Override
public void onScrollChanged(int i, boolean b, boolean b2) {
}
@Override
public void onDownMotionEvent() {
}
@Override
public void onUpOrCancelMotionEvent(ScrollState scrollState) {
}
}

View file

@ -0,0 +1,39 @@
package com.kabouzeid.gramophone.misc;
import android.view.GestureDetector;
import android.view.MotionEvent;
/**
* Created by karim on 20.12.14.
*/
public class SmallOnGestureListener implements GestureDetector.OnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
}

View file

@ -0,0 +1,35 @@
package com.kabouzeid.gramophone.misc;
import android.annotation.TargetApi;
import android.transition.Transition;
/**
* Created by karim on 20.12.14.
*/
@TargetApi(21)
public class SmallTransitionListener implements Transition.TransitionListener {
@Override
public void onTransitionStart(Transition transition) {
}
@Override
public void onTransitionEnd(Transition transition) {
}
@Override
public void onTransitionCancel(Transition transition) {
}
@Override
public void onTransitionPause(Transition transition) {
}
@Override
public void onTransitionResume(Transition transition) {
}
}

View file

@ -0,0 +1,54 @@
package com.kabouzeid.gramophone.model;
import android.content.Context;
import android.widget.ImageView;
import com.kabouzeid.gramophone.util.ImageLoaderUtil;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.nostra13.universalimageloader.core.ImageLoader;
/**
* Created by karim on 22.11.14.
*/
public class Album implements SearchEntry {
public int id;
public int artistId;
public String title;
public String artistName;
public int songCount;
public int year;
public Album(final int id, final String title, final String artistName, final int artistId,
final int songNumber, final int albumYear) {
this.id = id;
this.title = title;
this.artistName = artistName;
this.artistId = artistId;
songCount = songNumber;
year = albumYear;
}
public Album() {
this.id = -1;
this.title = "";
this.artistName = "";
songCount = -1;
year = -1;
}
@Override
public String getTitle() {
return title;
}
@Override
public String getSubTitle() {
return artistName;
}
@Override
public void loadImage(Context context, ImageView imageView) {
ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(id).toString(), imageView, new ImageLoaderUtil.defaultAlbumArtOnFailed());
}
}

View file

@ -0,0 +1,59 @@
package com.kabouzeid.gramophone.model;
import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ImageView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.lastfm.artist.LastFMArtistThumbnailLoader;
/**
* Created by karim on 29.12.14.
*/
public class Artist implements SearchEntry {
public int id;
public String name;
public int albumCount;
public int songCount;
public Artist(final int id, final String name, final int albumCount, final int songCount) {
this.id = id;
this.name = name;
this.songCount = songCount;
this.albumCount = albumCount;
}
public Artist() {
id = -1;
name = "";
songCount = -1;
albumCount = -1;
}
@Override
public String getTitle() {
return name;
}
@Override
public String getSubTitle() {
return songCount + " Songs | " + albumCount + " Albums";
}
@Override
public void loadImage(Context context, final ImageView imageView) {
imageView.setTag(name);
LastFMArtistThumbnailLoader.loadArtistThumbnail(context, name, new LastFMArtistThumbnailLoader.ArtistThumbnailLoaderCallback() {
@Override
public void onArtistThumbnailLoaded(Bitmap thumbnail) {
if (imageView.getTag().equals(name)) {
if (thumbnail != null) {
imageView.setImageBitmap(thumbnail);
} else {
imageView.setImageResource(R.drawable.default_artist_image);
}
}
}
});
}
}

View file

@ -0,0 +1,36 @@
package com.kabouzeid.gramophone.model;
/**
* Created by karim on 19.12.14.
*/
public class MusicRemoteEvent {
public static final int PLAY = 0;
public static final int PAUSE = 1;
public static final int RESUME = 2;
public static final int STOP = 3;
public static final int NEXT = 4;
public static final int PREV = 5;
public static final int TRACK_CHANGED = 6;
public static final int SONG_COMPLETED = 7;
public static final int QUEUE_COMPLETED = 8;
public static final int SERVICE_CONNECTED = 9;
public static final int SERVICE_DISCONNECTED = 10;
public static final int STATE_SAVED = 11;
public static final int STATE_RESTORED = 12;
public static final int SHUFFLE_MODE_CHANGED = 13;
public static final int REPEAT_MODE_CHANGED = 14;
private int action;
public MusicRemoteEvent(int action) {
this.action = action;
}
public int getAction() {
return action;
}
}

View file

@ -0,0 +1,14 @@
package com.kabouzeid.gramophone.model;
/**
* Created by karim on 23.11.14.
*/
public class NavigationDrawerItem {
public String title;
public int imageRes;
public NavigationDrawerItem(String title, int imageRes) {
this.title = title;
this.imageRes = imageRes;
}
}

View file

@ -0,0 +1,15 @@
package com.kabouzeid.gramophone.model;
import android.content.Context;
import android.widget.ImageView;
/**
* Created by karim on 27.02.15.
*/
public interface SearchEntry {
public String getTitle();
public String getSubTitle();
public void loadImage(Context context, ImageView imageView);
}

View file

@ -0,0 +1,62 @@
package com.kabouzeid.gramophone.model;
import android.content.Context;
import android.widget.ImageView;
import com.kabouzeid.gramophone.util.ImageLoaderUtil;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.io.Serializable;
/**
* Created by karim on 23.11.14.
*/
public class Song implements Serializable, SearchEntry {
public int id;
public int albumId;
public int artistId;
public String title;
public String artistName;
public String albumName;
public long duration;
public int trackNumber;
public Song(final int id, final int albumId, final int artistId, final String title, final String artistName,
final String albumName, final long duration, final int trackNumber) {
this.id = id;
this.albumId = albumId;
this.artistId = artistId;
this.title = title;
this.artistName = artistName;
this.albumName = albumName;
this.duration = duration;
this.trackNumber = trackNumber;
}
public Song() {
this.id = -1;
this.albumId = -1;
this.artistId = -1;
this.title = "";
this.artistName = "";
this.albumName = "";
this.duration = -1;
this.trackNumber = -1;
}
@Override
public String getTitle() {
return title;
}
@Override
public String getSubTitle() {
return artistName;
}
@Override
public void loadImage(Context context, ImageView imageView) {
ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(albumId).toString(), imageView, new ImageLoaderUtil.defaultAlbumArtOnFailed());
}
}

View file

@ -0,0 +1,105 @@
package com.kabouzeid.gramophone.provider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class AlbumJSONStore extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "albumJSONLastFM.db";
private static final int VERSION = 1;
private static AlbumJSONStore sInstance = null;
public AlbumJSONStore(final Context context) {
super(context, DATABASE_NAME, null, VERSION);
}
public static synchronized AlbumJSONStore getInstance(final Context context) {
if (sInstance == null) {
sInstance = new AlbumJSONStore(context.getApplicationContext());
}
return sInstance;
}
public static void deleteDatabase(final Context context) {
context.deleteDatabase(DATABASE_NAME);
}
public void addAlbumJSON(final String albumAndArtistName, final String JSON) {
if (albumAndArtistName == null || JSON == null) {
return;
}
final SQLiteDatabase database = getWritableDatabase();
final ContentValues values = new ContentValues(2);
database.beginTransaction();
values.put(AlbumJSONColumns.ALBUMANDARTIST_NAME, albumAndArtistName.trim().toLowerCase());
values.put(AlbumJSONColumns.JSON, JSON);
database.insert(AlbumJSONColumns.NAME, null, values);
database.setTransactionSuccessful();
database.endTransaction();
}
public String getAlbumJSON(final String albumAndArtistName) {
if (albumAndArtistName == null) {
return null;
}
final SQLiteDatabase database = getReadableDatabase();
final String[] projection = new String[]{
AlbumJSONColumns.JSON,
AlbumJSONColumns.ALBUMANDARTIST_NAME
};
final String selection = AlbumJSONColumns.ALBUMANDARTIST_NAME + "=?";
final String[] having = new String[]{
albumAndArtistName.trim().toLowerCase()
};
Cursor cursor = database.query(AlbumJSONColumns.NAME, projection, selection, having, null,
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final String JSON = cursor.getString(cursor.getColumnIndexOrThrow(AlbumJSONColumns.JSON));
cursor.close();
return JSON;
}
if (cursor != null) {
cursor.close();
}
return null;
}
public void removeItem(final String albumAndArtistName) {
final SQLiteDatabase database = getReadableDatabase();
database.delete(AlbumJSONColumns.NAME, AlbumJSONColumns.ALBUMANDARTIST_NAME + " = ?", new String[]{
albumAndArtistName.trim().toLowerCase()
});
}
public interface AlbumJSONColumns {
public static final String NAME = "AlbumJSON";
public static final String ALBUMANDARTIST_NAME = "AlbumAndArtistName";
public static final String JSON = "JSON";
}
@Override
public void onCreate(final SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + AlbumJSONColumns.NAME +
" (" + AlbumJSONColumns.ALBUMANDARTIST_NAME + " TEXT NOT NULL," +
AlbumJSONColumns.JSON + " TEXT NOT NULL);"
);
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + AlbumJSONColumns.NAME);
onCreate(db);
}
}

View file

@ -0,0 +1,105 @@
package com.kabouzeid.gramophone.provider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class ArtistJSONStore extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "artistJSONLastFM.db";
private static final int VERSION = 1;
private static ArtistJSONStore sInstance = null;
public ArtistJSONStore(final Context context) {
super(context, DATABASE_NAME, null, VERSION);
}
public static synchronized ArtistJSONStore getInstance(final Context context) {
if (sInstance == null) {
sInstance = new ArtistJSONStore(context.getApplicationContext());
}
return sInstance;
}
public static void deleteDatabase(final Context context) {
context.deleteDatabase(DATABASE_NAME);
}
public void addArtistJSON(final String artistName, final String JSON) {
if (artistName == null || JSON == null) {
return;
}
final SQLiteDatabase database = getWritableDatabase();
final ContentValues values = new ContentValues(2);
database.beginTransaction();
values.put(ArtistJSONColumns.ARTIST_NAME, artistName.trim().toLowerCase());
values.put(ArtistJSONColumns.JSON, JSON);
database.insert(ArtistJSONColumns.NAME, null, values);
database.setTransactionSuccessful();
database.endTransaction();
}
public String getArtistJSON(final String artistName) {
if (artistName == null) {
return null;
}
final SQLiteDatabase database = getReadableDatabase();
final String[] projection = new String[]{
ArtistJSONColumns.JSON,
ArtistJSONColumns.ARTIST_NAME
};
final String selection = ArtistJSONColumns.ARTIST_NAME + "=?";
final String[] having = new String[]{
artistName.trim().toLowerCase()
};
Cursor cursor = database.query(ArtistJSONColumns.NAME, projection, selection, having, null,
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
final String JSON = cursor.getString(cursor.getColumnIndexOrThrow(ArtistJSONColumns.JSON));
cursor.close();
return JSON;
}
if (cursor != null) {
cursor.close();
}
return null;
}
public void removeItem(final String artistName) {
final SQLiteDatabase database = getReadableDatabase();
database.delete(ArtistJSONColumns.NAME, ArtistJSONColumns.ARTIST_NAME + " = ?", new String[]{
artistName.trim().toLowerCase()
});
}
public interface ArtistJSONColumns {
public static final String NAME = "ArtistJSON";
public static final String ARTIST_NAME = "ArtistName";
public static final String JSON = "JSON";
}
@Override
public void onCreate(final SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + ArtistJSONColumns.NAME +
" (" + ArtistJSONColumns.ARTIST_NAME + " TEXT NOT NULL," +
ArtistJSONColumns.JSON + " TEXT NOT NULL);"
);
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + ArtistJSONColumns.NAME);
onCreate(db);
}
}

View file

@ -0,0 +1,70 @@
package com.kabouzeid.gramophone.service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.KeyEvent;
public class MediaButtonIntentReceiver extends BroadcastReceiver {
public static final String TAG = MediaButtonIntentReceiver.class.getSimpleName();
private static final int DOUBLE_CLICK = 500;
private static long mLastClickTime = 0;
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) {
Log.i(TAG, intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT).toString());
final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (event == null)
return;
final int keycode = event.getKeyCode();
final int action = event.getAction();
final long eventTime = event.getEventTime();
String command = null;
switch (keycode) {
case KeyEvent.KEYCODE_MEDIA_STOP:
command = MusicService.ACTION_STOP;
break;
case KeyEvent.KEYCODE_HEADSETHOOK:
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
case KeyEvent.KEYCODE_MEDIA_PLAY:
command = MusicService.ACTION_TOGGLE_PLAYBACK;
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
command = MusicService.ACTION_SKIP;
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
command = MusicService.ACTION_REWIND;
break;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
command = MusicService.ACTION_PAUSE;
break;
}
if (command != null) {
if (action == KeyEvent.ACTION_DOWN) {
if (event.getRepeatCount() == 0) {
/**
* If another app received the broadcast first, this if statement will skip.
*/
//TODO triple click to rewind
final Intent i = new Intent(context, MusicService.class);
if (keycode == KeyEvent.KEYCODE_HEADSETHOOK
&& eventTime - mLastClickTime < DOUBLE_CLICK) {
i.setAction(MusicService.ACTION_SKIP);
mLastClickTime = 0;
} else {
i.setAction(command);
mLastClickTime = eventTime;
}
context.startService(i);
}
}
if (isOrderedBroadcast())
abortBroadcast();
}
}
}
}

View file

@ -0,0 +1,655 @@
package com.kabouzeid.gramophone.service;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.RemoteControlClient;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.helper.PlayingNotificationHelper;
import com.kabouzeid.gramophone.helper.ShuffleHelper;
import com.kabouzeid.gramophone.interfaces.OnMusicRemoteEventListener;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.MusicRemoteEvent;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.util.InternalStorageUtil;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.nostra13.universalimageloader.core.ImageLoader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class MusicService extends Service implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, AudioManager.OnAudioFocusChangeListener {
public static final String ACTION_TOGGLE_PLAYBACK = "com.kabouzeid.gramophone.action.TOGGLE_PLAYBACK";
public static final String ACTION_PLAY = "com.kabouzeid.gramophone.action.PLAY";
public static final String ACTION_PAUSE = "com.kabouzeid.gramophone.action.PAUSE";
public static final String ACTION_STOP = "com.kabouzeid.gramophone.action.STOP";
public static final String ACTION_SKIP = "com.kabouzeid.gramophone.action.SKIP";
public static final String ACTION_REWIND = "com.kabouzeid.gramophone.action.REWIND";
public static final String ACTION_QUIT = "com.kabouzeid.gramophone.action.QUIT";
public static final int SHUFFLE_MODE_NONE = 0;
public static final int SHUFFLE_MODE_SHUFFLE = 1;
public static final int REPEAT_MODE_NONE = 0;
public static final int REPEAT_MODE_ALL = 1;
public static final int REPEAT_MODE_THIS = 2;
private static final String TAG = MusicService.class.getSimpleName();
private final IBinder musicBind = new MusicBinder();
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
pausePlaying();
}
}
};
private MediaPlayer player;
private List<Song> playingQueue;
private List<Song> originalPlayingQueue;
private List<OnMusicRemoteEventListener> onMusicRemoteEventListeners;
private int currentSongId = -1;
private int position = -1;
private int shuffleMode;
private int repeatMode;
private boolean isPlayerPrepared;
private boolean wasPlayingBeforeFocusLoss;
private boolean thingsRegistered;
private boolean saveQueuesAgain;
private boolean isSavingQueues;
private PlayingNotificationHelper playingNotificationHelper;
private AudioManager audioManager;
private RemoteControlClient remoteControlClient;
public MusicService() {
}
@Override
public void onCreate() {
super.onCreate();
isPlayerPrepared = false;
playingQueue = new ArrayList<>();
originalPlayingQueue = new ArrayList<>();
onMusicRemoteEventListeners = new ArrayList<>();
playingNotificationHelper = new PlayingNotificationHelper(this);
shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_SHUFFLE_MODE, 0);
repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_REPEAT_MODE, 0);
registerEverything();
}
private void registerEverything() {
if (!thingsRegistered) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(receiver, intentFilter);
getAudioManager().registerMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class));
initRemoteControlClient();
thingsRegistered = true;
}
}
private AudioManager getAudioManager() {
if (audioManager == null) {
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
}
return audioManager;
}
private void initRemoteControlClient() {
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class));
PendingIntent mediaPendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, mediaButtonIntent, 0);
remoteControlClient = new RemoteControlClient(mediaPendingIntent);
remoteControlClient.setTransportControlFlags(
RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS);
getAudioManager().registerRemoteControlClient(remoteControlClient);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
setUpMediaPlayerIfNeeded();
if (intent != null) {
if (intent.getAction() != null) {
String action = intent.getAction();
switch (action) {
case ACTION_TOGGLE_PLAYBACK:
if (isPlaying()) {
pausePlaying();
} else {
resumePlaying();
}
break;
case ACTION_PAUSE:
pausePlaying();
break;
case ACTION_PLAY:
playSong();
break;
case ACTION_REWIND:
back();
break;
case ACTION_SKIP:
playNextSong();
break;
case ACTION_STOP:
stopPlaying();
break;
case ACTION_QUIT:
killEverythingAndReleaseResources();
}
}
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
unregisterEverything();
killEverythingAndReleaseResources();
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind");
return musicBind;
}
@Override
public boolean onUnbind(Intent intent) {
unregisterEverything();
killEverythingAndReleaseResources();
return false;
}
private void unregisterEverything() {
if (thingsRegistered) {
unregisterReceiver(receiver);
getAudioManager().unregisterRemoteControlClient(remoteControlClient);
getAudioManager().unregisterMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class));
getAudioManager().abandonAudioFocus(this);
thingsRegistered = false;
}
}
private void killEverythingAndReleaseResources() {
stopPlaying();
playingNotificationHelper.killNotification();
savePosition();
saveQueues();
stopSelf();
}
public void stopPlaying() {
isPlayerPrepared = false;
if (player != null) {
player.stop();
player.release();
player = null;
}
playingNotificationHelper.updatePlayState(isPlaying());
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STOP);
}
public boolean isPlaying() {
return player != null && isPlayerPrepared && player.isPlaying();
}
private void notifyOnMusicRemoteEventListeners(int event) {
MusicRemoteEvent musicRemoteEvent = new MusicRemoteEvent(event);
for (OnMusicRemoteEventListener listener : onMusicRemoteEventListeners) {
listener.onMusicRemoteEvent(musicRemoteEvent);
}
}
public void saveQueues() {
try {
InternalStorageUtil.writeObject(MusicService.this, AppKeys.IS_PLAYING_QUEUE, playingQueue);
InternalStorageUtil.writeObject(MusicService.this, AppKeys.IS_ORIGINAL_PLAYING_QUEUE, originalPlayingQueue);
Log.i(TAG, "saved current queue state");
} catch (IOException e) {
Log.e(TAG, "error while saving music service queue state", e);
}
}
public void savePosition() {
new Thread(new Runnable() {
@Override
public void run() {
try {
InternalStorageUtil.writeObject(MusicService.this, AppKeys.IS_POSITION_IN_QUEUE, getPosition());
Log.i(TAG, "saved current position state");
} catch (IOException e) {
Log.e(TAG, "error while saving music service position state", e);
}
}
}).start();
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
@Override
public void onCompletion(MediaPlayer mp) {
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.SONG_COMPLETED);
if (isLastTrack() && getRepeatMode() == REPEAT_MODE_NONE) {
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.QUEUE_COMPLETED);
playingNotificationHelper.updatePlayState(isPlaying());
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STOP);
} else {
playNextSong();
}
}
public void playNextSong() {
if (position != -1) {
if (isPlayerPrepared) {
setPosition(getNextPosition());
playSong();
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.NEXT);
}
}
}
public void playSong() {
if (requestFocus()) {
setUpMediaPlayerIfNeeded();
registerEverything();
isPlayerPrepared = false;
player.reset();
try {
Uri trackUri = getCurrentPositionTrackUri();
player.setDataSource(getApplicationContext(), trackUri);
currentSongId = getPlayingQueue().get(getPosition()).id;
updateNotification();
updateRemoteControlClient();
player.prepareAsync();
} catch (Exception e) {
Log.e("MUSIC SERVICE", "Error setting data source", e);
player.reset();
Toast.makeText(getApplicationContext(), getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STOP);
playingNotificationHelper.updatePlayState(false);
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
}
}
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.TRACK_CHANGED);
}
private boolean requestFocus() {
int result = getAudioManager().requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
return (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
}
private void updateRemoteControlClient() {
Song song = getPlayingQueue().get(getPosition());
Bitmap loadedImage = ImageLoader.getInstance().loadImageSync(MusicUtil.getAlbumArtUri(song.albumId).toString());
remoteControlClient
.editMetadata(false)
.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, song.artistName)
.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, song.title)
.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, song.duration)
.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, loadedImage)
.apply();
}
private void setUpMediaPlayerIfNeeded() {
if (player == null) {
player = new MediaPlayer();
player.setOnPreparedListener(this);
player.setOnCompletionListener(this);
player.setOnErrorListener(this);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
}
}
private void updateNotification() {
playingNotificationHelper.buildNotification(playingQueue.get(position), isPlaying());
}
private Uri getCurrentPositionTrackUri() {
return ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, playingQueue.get(position).id);
}
public int getNextPosition() {
int position = 0;
switch (repeatMode) {
case REPEAT_MODE_NONE:
position = getPosition() + 1;
if (isLastTrack()) {
position -= 1;
}
break;
case REPEAT_MODE_ALL:
position = getPosition() + 1;
if (isLastTrack()) {
position = 0;
}
break;
case REPEAT_MODE_THIS:
position = getPosition();
break;
}
return position;
}
private boolean isLastTrack() {
return getPosition() == getPlayingQueue().size() - 1;
}
public List<Song> getPlayingQueue() {
return playingQueue;
}
public int getRepeatMode() {
return repeatMode;
}
public void setRepeatMode(final int repeatMode) {
switch (repeatMode) {
case REPEAT_MODE_NONE:
case REPEAT_MODE_ALL:
case REPEAT_MODE_THIS:
this.repeatMode = repeatMode;
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putInt(AppKeys.SP_REPEAT_MODE, repeatMode)
.apply();
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.REPEAT_MODE_CHANGED);
break;
}
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
isPlayerPrepared = false;
player.reset();
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.STOP);
return false;
}
@Override
public void onPrepared(MediaPlayer mp) {
player.start();
isPlayerPrepared = true;
playingNotificationHelper.updatePlayState(isPlaying());
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.PLAY);
savePosition();
}
public void openQueue(final List<Song> playingQueue, final int startPosition, final boolean startPlaying) {
if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue.size()) {
originalPlayingQueue = playingQueue;
this.playingQueue = new ArrayList<>(originalPlayingQueue);
setPosition(startPosition);
if (shuffleMode == SHUFFLE_MODE_SHUFFLE) {
ShuffleHelper.makeShuffleList(this.playingQueue, startPosition);
setPosition(0);
}
if (startPlaying) {
playSong();
}
saveState();
}
}
public void saveState() {
saveQueuesAsync();
savePosition();
}
public void saveQueuesAsync() {
if (isSavingQueues) {
saveQueuesAgain = true;
} else {
new Thread(new Runnable() {
@Override
public void run() {
isSavingQueues = true;
do {
saveQueuesAgain = false;
saveQueues();
} while (saveQueuesAgain);
isSavingQueues = false;
}
}).start();
}
}
public void restorePreviousState(final List<Song> originalPlayingQueue, final List<Song> playingQueue, int position) {
this.originalPlayingQueue = originalPlayingQueue;
this.playingQueue = playingQueue;
this.position = position;
saveState();
}
public long getCurrentSongId() {
return currentSongId;
}
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// resume playback
registerEverything();
setUpMediaPlayerIfNeeded();
player.setVolume(1.0f, 1.0f);
if (wasPlayingBeforeFocusLoss) {
resumePlaying();
}
updateRemoteControlClient();
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
//TODO maybe also release player (stopPlaying()) but then the current position in the song is 0 again
wasPlayingBeforeFocusLoss = false;
pausePlaying();
unregisterEverything();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
wasPlayingBeforeFocusLoss = isPlaying();
pausePlaying();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Lost focus for a short time, but it's ok to keep playing
// at an attenuated level
if (!isPlayerPrepared()) {
setUpMediaPlayerIfNeeded();
}
player.setVolume(0.2f, 0.2f);
break;
}
}
public void playSongAt(final int position) {
if (position < getPlayingQueue().size() && position >= 0) {
setPosition(position);
playSong();
} else {
Log.e(TAG, "No song in queue at given index!");
}
}
public void pausePlaying() {
if (isPlaying()) {
player.pause();
playingNotificationHelper.updatePlayState(isPlaying());
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.PAUSE);
}
}
public void resumePlaying() {
if (requestFocus()) {
if (isPlayerPrepared) {
player.start();
playingNotificationHelper.updatePlayState(isPlaying());
remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.RESUME);
} else {
playSong();
}
} else {
Toast.makeText(this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT).show();
}
}
public void playPreviousSong() {
if (position != -1) {
setPosition(getPreviousPosition());
playSong();
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.PREV);
}
}
public void back() {
if (position != -1) {
if (getSongProgressMillis() > 2000) {
seekTo(0);
} else {
playPreviousSong();
}
}
}
public int getPreviousPosition() {
int position = 0;
switch (repeatMode) {
case REPEAT_MODE_NONE:
position = getPosition() - 1;
if (position < 0) {
position = 0;
}
break;
case REPEAT_MODE_ALL:
position = getPosition() - 1;
if (position < 0) {
position = getPlayingQueue().size() - 1;
}
break;
case REPEAT_MODE_THIS:
position = getPosition();
break;
}
return position;
}
public int getSongProgressMillis() {
return player.getCurrentPosition();
}
public int getSongDurationMillis() {
return player.getDuration();
}
public void seekTo(int millis) {
player.seekTo(millis);
}
public boolean isPlayerPrepared() {
return player != null && isPlayerPrepared;
}
public void addOnMusicRemoteEventListener(OnMusicRemoteEventListener onMusicRemoteEventListener) {
onMusicRemoteEventListeners.add(onMusicRemoteEventListener);
}
public void cycleRepeatMode() {
switch (getRepeatMode()) {
case REPEAT_MODE_NONE:
setRepeatMode(REPEAT_MODE_ALL);
break;
case REPEAT_MODE_ALL:
setRepeatMode(REPEAT_MODE_THIS);
break;
default:
setRepeatMode(REPEAT_MODE_NONE);
break;
}
}
public void toggleShuffle() {
if (getShuffleMode() == SHUFFLE_MODE_NONE) {
setShuffleMode(SHUFFLE_MODE_SHUFFLE);
} else {
setShuffleMode(SHUFFLE_MODE_NONE);
}
}
public int getShuffleMode() {
return shuffleMode;
}
public void setShuffleMode(final int shuffleMode) {
switch (shuffleMode) {
case SHUFFLE_MODE_SHUFFLE:
this.shuffleMode = shuffleMode;
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putInt(AppKeys.SP_SHUFFLE_MODE, shuffleMode)
.apply();
ShuffleHelper.makeShuffleList(this.playingQueue, getPosition());
setPosition(0);
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.SHUFFLE_MODE_CHANGED);
break;
case SHUFFLE_MODE_NONE:
this.shuffleMode = shuffleMode;
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putInt(AppKeys.SP_SHUFFLE_MODE, shuffleMode)
.apply();
playingQueue = new ArrayList<>(originalPlayingQueue);
int newPosition = 0;
for (Song song : playingQueue) {
if (song.id == currentSongId) {
newPosition = playingQueue.indexOf(song);
}
}
setPosition(newPosition);
notifyOnMusicRemoteEventListeners(MusicRemoteEvent.SHUFFLE_MODE_CHANGED);
break;
}
}
public class MusicBinder extends Binder {
public MusicService getService() {
return MusicService.this;
}
}
}

View file

@ -0,0 +1,416 @@
package com.kabouzeid.gramophone.ui.activities;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.TextView;
import com.github.ksoichiro.android.observablescrollview.ObservableListView;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.songadapter.SongAdapter;
import com.kabouzeid.gramophone.comparator.SongTrackNumberComparator;
import com.kabouzeid.gramophone.interfaces.KabViewsDisableAble;
import com.kabouzeid.gramophone.interfaces.OnMusicRemoteEventListener;
import com.kabouzeid.gramophone.loader.AlbumLoader;
import com.kabouzeid.gramophone.loader.AlbumSongLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.misc.SmallObservableScrollViewCallbacks;
import com.kabouzeid.gramophone.model.Album;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.base.AbsFabActivity;
import com.kabouzeid.gramophone.ui.activities.tageditor.AlbumTagEditorActivity;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.Util;
import com.kabouzeid.gramophone.util.ViewUtil;
import com.nhaarman.listviewanimations.appearance.AnimationAdapter;
import com.nhaarman.listviewanimations.appearance.simple.ScaleInAnimationAdapter;
import com.nineoldandroids.view.ViewHelper;
import com.nineoldandroids.view.ViewPropertyAnimator;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import java.util.List;
/*
*
* A lot of hackery is done in this activity. Changing things may will brake the whole activity.
*
* Should be kinda stable ONLY AS IT IS!!!
*
* */
public class AlbumDetailActivity extends AbsFabActivity implements OnMusicRemoteEventListener, KabViewsDisableAble {
public static final String TAG = AlbumDetailActivity.class.getSimpleName();
private static final boolean TOOLBAR_IS_STICKY = true;
private static final int DEFAULT_DELAY_NO_TRANSITION = 200;
private static final int DEFAULT_DELAY = 450;
private static final int DEFAULT_ANIMATION_TIME = 1000;
private App app;
private Album album;
private AnimationAdapter animatedSongsAdapter;
private ObservableListView absSongListView;
private View statusBar;
private ImageView albumArtImageView;
private View albumArtOverlayView;
private View songsBackgroundView;
private TextView albumTitleView;
private Toolbar toolbar;
private int toolbarHeight;
private int headerOffset;
private int titleViewHeight;
private int albumArtViewHeight;
private int toolbarColor;
private SmallObservableScrollViewCallbacks observableScrollViewCallbacks = new SmallObservableScrollViewCallbacks() {
@Override
public void onScrollChanged(int scrollY, boolean b, boolean b2) {
super.onScrollChanged(scrollY, b, b2);
// Translate overlay and image
float flexibleRange = albumArtViewHeight - headerOffset;
int minOverlayTransitionY = headerOffset - albumArtOverlayView.getHeight();
ViewHelper.setTranslationY(albumArtOverlayView, Math.max(minOverlayTransitionY, Math.min(0, -scrollY)));
ViewHelper.setTranslationY(albumArtImageView, Math.max(minOverlayTransitionY, Math.min(0, -scrollY / 2)));
// Translate list background
ViewHelper.setTranslationY(songsBackgroundView, Math.max(0, -scrollY + albumArtViewHeight));
// Change alpha of overlay
ViewHelper.setAlpha(albumArtOverlayView, Math.max(0, Math.min(1, (float) scrollY / flexibleRange)));
// Translate name text
int maxTitleTranslationY = albumArtViewHeight;
int titleTranslationY = maxTitleTranslationY - scrollY;
if (TOOLBAR_IS_STICKY) {
titleTranslationY = Math.max(headerOffset, titleTranslationY);
}
ViewHelper.setTranslationY(albumTitleView, titleTranslationY);
// Translate FAB
int fabTranslationY = titleTranslationY + titleViewHeight - (getFab().getHeight() / 2);
ViewHelper.setTranslationY(getFab(), fabTranslationY);
if (TOOLBAR_IS_STICKY) {
// Change alpha of toolbar background
if (-scrollY + albumArtViewHeight <= headerOffset) {
ViewUtil.setBackgroundAlpha(toolbar, 1, toolbarColor);
ViewUtil.setBackgroundAlpha(statusBar, 1, toolbarColor);
} else {
ViewUtil.setBackgroundAlpha(toolbar, 0, toolbarColor);
ViewUtil.setBackgroundAlpha(statusBar, 0, toolbarColor);
}
} else {
// Translate Toolbar
if (scrollY < albumArtViewHeight) {
ViewHelper.setTranslationY(toolbar, 0);
} else {
ViewHelper.setTranslationY(toolbar, -scrollY);
}
}
}
};
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Util.hasLollipopSDK()) postponeEnterTransition();
app = (App) getApplicationContext();
setTheme(app.getAppTheme());
setUpTranslucence();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_album_detail);
Bundle intentExtras = getIntent().getExtras();
int albumId = -1;
if (intentExtras != null) {
albumId = intentExtras.getInt(AppKeys.E_ALBUM);
}
album = AlbumLoader.getAlbum(this, albumId);
if (album.id == -1) {
finish();
}
initViews();
setUpObservableListViewParams();
setUpToolBar();
setUpViews();
if (!Util.hasLollipopSDK()) animateEnterActivity();
}
@Override
public String getTag() {
return TAG;
}
@Override
public void goToAlbum(int albumId) {
if (album.id != albumId) {
goToAlbum(albumId);
}
}
private void initViews() {
albumArtImageView = (ImageView) findViewById(R.id.album_art);
toolbar = (Toolbar) findViewById(R.id.toolbar);
albumArtOverlayView = findViewById(R.id.overlay);
absSongListView = (ObservableListView) findViewById(R.id.list);
albumTitleView = (TextView) findViewById(R.id.album_title);
songsBackgroundView = findViewById(R.id.list_background);
statusBar = findViewById(R.id.statusBar);
}
private void setUpObservableListViewParams() {
albumArtViewHeight = getResources().getDimensionPixelSize(R.dimen.header_image_height);
toolbarColor = getResources().getColor(R.color.materialmusic_default_bar_color);
toolbarHeight = Util.getActionBarSize(this);
titleViewHeight = getResources().getDimensionPixelSize(R.dimen.title_view_height);
headerOffset = toolbarHeight;
headerOffset += getResources().getDimensionPixelSize(R.dimen.statusMargin);
}
private void setUpViews() {
albumTitleView.setText(album.title);
ViewHelper.setAlpha(albumArtOverlayView, 0);
prepareViewsForOpenAnimation();
setUpAlbumArtAndApplyPalette();
setUpListView();
}
private void prepareViewsForOpenAnimation() {
albumTitleView.setPivotY(0);
albumTitleView.setScaleY(0);
}
@SuppressLint("NewApi")
private void setUpAlbumArtAndApplyPalette() {
ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(album.id).toString(), albumArtImageView, new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
albumArtImageView.setImageResource(R.drawable.default_album_art);
if (Util.hasLollipopSDK()) {
startPostponedEnterTransition();
animateEnterActivity();
}
}
@Override
public void onLoadingComplete(String imageUri, final View view, Bitmap loadedImage) {
applyPalette(loadedImage);
if (Util.hasLollipopSDK()) {
startPostponedEnterTransition();
animateEnterActivity();
}
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
albumArtImageView.setImageResource(R.drawable.default_album_art);
if (Util.hasLollipopSDK()) {
startPostponedEnterTransition();
animateEnterActivity();
}
}
});
}
private void applyPalette(Bitmap bitmap) {
Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
Palette.Swatch swatch = palette.getVibrantSwatch();
if (swatch != null) {
toolbarColor = swatch.getRgb();
albumArtOverlayView.setBackgroundColor(swatch.getRgb());
albumTitleView.setBackgroundColor(swatch.getRgb());
albumTitleView.setTextColor(swatch.getTitleTextColor());
}
}
});
}
private void setUpListView() {
absSongListView.setScrollViewCallbacks(observableScrollViewCallbacks);
setListViewPadding();
final View contentView = getWindow().getDecorView().findViewById(android.R.id.content);
contentView.post(new Runnable() {
@Override
public void run() {
songsBackgroundView.getLayoutParams().height = contentView.getHeight();
observableScrollViewCallbacks.onScrollChanged(0, false, false);
}
});
}
private void setListViewPadding() {
setListViewPaddingTop();
if (app.isInPortraitMode() || app.isTablet()) {
setListViewPaddingBottom();
}
}
private void setListViewPaddingTop() {
final View paddingView = new View(AlbumDetailActivity.this);
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
albumArtViewHeight + titleViewHeight);
paddingView.setLayoutParams(lp);
paddingView.setClickable(true);
absSongListView.addHeaderView(paddingView);
}
private void setListViewPaddingBottom() {
final View paddingView = new View(AlbumDetailActivity.this);
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
Util.getNavigationBarHeight(this));
paddingView.setLayoutParams(lp);
paddingView.setClickable(true);
absSongListView.addFooterView(paddingView);
}
private void setUpToolBar() {
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(null);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (!TOOLBAR_IS_STICKY) {
toolbar.setBackgroundColor(Color.TRANSPARENT);
}
}
private void setUpTranslucence() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Util.setStatusBarTranslucent(getWindow(), true);
if (app.isInPortraitMode() || app.isTablet()) {
Util.setNavBarTranslucent(getWindow(), true);
}
}
}
private void animateEnterActivity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
animateHeader(0);
setUpSongsAdapter();
}
}, DEFAULT_DELAY);
} else {
setUpSongsAdapter();
getFab().setScaleX(0);
getFab().setScaleY(0);
animateHeader(DEFAULT_DELAY_NO_TRANSITION);
animateFab(DEFAULT_DELAY_NO_TRANSITION);
}
}
private void setUpSongsAdapter() {
final List<Song> songs = AlbumSongLoader.getAlbumSongList(this, album.id, new SongTrackNumberComparator());
final SongAdapter songAdapter = new SongAdapter(this, this, songs);
// SwingBottomInAnimationAdapter songsAdapter = new SwingBottomInAnimationAdapter(songAdapter);
// SwingRightInAnimationAdapter songsAdapter = new SwingRightInAnimationAdapter(songAdapter);
// SwingLeftInAnimationAdapter songsAdapter = new SwingLeftInAnimationAdapter(songAdapter);
ScaleInAnimationAdapter songsAdapter = new ScaleInAnimationAdapter(songAdapter);
// AlphaInAnimationAdapter songsAdapter = new AlphaInAnimationAdapter(songAdapter);
animatedSongsAdapter = songsAdapter;
animatedSongsAdapter.setAbsListView(absSongListView);
absSongListView.setAdapter(animatedSongsAdapter);
absSongListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (position > 0) {
app.getMusicPlayerRemote().openQueue(songs, position - 1, true);
}
}
});
}
private void animateHeader(int startDelay) {
ViewPropertyAnimator.animate(albumTitleView)
.scaleX(1)
.scaleY(1)
.setInterpolator(new DecelerateInterpolator(4))
.setDuration(DEFAULT_ANIMATION_TIME)
.setStartDelay(startDelay)
.start();
}
private void animateFab(int startDelay) {
ViewPropertyAnimator.animate(getFab())
.scaleX(1)
.scaleY(1)
.setInterpolator(new DecelerateInterpolator(4))
.setDuration(DEFAULT_ANIMATION_TIME)
.setStartDelay(startDelay)
.start();
}
@Override
public void enableViews() {
super.enableViews();
absSongListView.setEnabled(true);
toolbar.setEnabled(true);
}
@Override
public void disableViews() {
super.disableViews();
absSongListView.setEnabled(false);
toolbar.setEnabled(false);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_album_detail, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home:
super.onBackPressed();
return true;
case R.id.action_settings:
return true;
case R.id.action_current_playing:
return openCurrentPlayingIfPossible(null);
case R.id.action_tag_editor:
Intent intent = new Intent(this, AlbumTagEditorActivity.class);
intent.putExtra(AppKeys.E_ID, album.id);
startActivity(intent);
return true;
case R.id.action_go_to_artist:
goToArtist(album.artistId, null);
return true;
}
return super.onOptionsItemSelected(item);
}
}

View file

@ -0,0 +1,501 @@
package com.kabouzeid.gramophone.ui.activities;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.Toolbar;
import android.transition.Transition;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks;
import com.github.ksoichiro.android.observablescrollview.ScrollState;
import com.google.samples.apps.iosched.ui.widget.SlidingTabLayout;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.interfaces.KabViewsDisableAble;
import com.kabouzeid.gramophone.interfaces.OnMusicRemoteEventListener;
import com.kabouzeid.gramophone.lastfm.artist.LastFMArtistImageLoader;
import com.kabouzeid.gramophone.loader.ArtistLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.Artist;
import com.kabouzeid.gramophone.ui.activities.base.AbsFabActivity;
import com.kabouzeid.gramophone.ui.fragments.artistviewpager.AbsViewPagerTabArtistListFragment;
import com.kabouzeid.gramophone.ui.fragments.artistviewpager.ViewPagerTabArtistAlbumFragment;
import com.kabouzeid.gramophone.ui.fragments.artistviewpager.ViewPagerTabArtistBioFragment;
import com.kabouzeid.gramophone.ui.fragments.artistviewpager.ViewPagerTabArtistSongListFragment;
import com.kabouzeid.gramophone.util.Util;
import com.kabouzeid.gramophone.util.ViewUtil;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.view.ViewHelper;
import com.nineoldandroids.view.ViewPropertyAnimator;
/*
*
* A lot of hackery is done in this activity. Changing things may will brake the whole activity.
*
* Should be kinda stable ONLY AS IT IS!!!
*
* */
public class ArtistDetailActivity extends AbsFabActivity implements OnMusicRemoteEventListener, KabViewsDisableAble, ObservableScrollViewCallbacks {
public static final String TAG = ArtistDetailActivity.class.getSimpleName();
public static final String ARG_ARTIST_ID = "com.kabouzeid.gramophone.artist.id";
public static final String ARG_ARTIST_NAME = "com.kabouzeid.gramophone.artist.name";
private static final boolean TOOLBAR_IS_STICKY = true;
private boolean isAnimating;
private Artist artist;
private SlidingTabLayout slidingTabs;
private View statusBar;
private ImageView artistImageView;
private View artistArtOverlayView;
private View absAlbumListBackgroundView;
private TextView artistTitleText;
private Toolbar toolbar;
private ViewPager viewPager;
private NavigationAdapter navigationAdapter;
private int toolbarHeight;
private int headerOffset;
private int titleViewHeight;
private int artistImageViewHeight;
private int toolbarColor;
private int tabHeight;
private Fragment currentFragment;
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
setUpTranslucence(true, true);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_artist_detail);
getIntentExtras();
initViews();
setUpObservableListViewParams();
setUpToolBar();
setUpViews();
if (Util.hasLollipopSDK()) {
getWindow().getSharedElementEnterTransition().addListener(new Transition.TransitionListener() {
@Override
public void onTransitionStart(Transition transition) {
}
@Override
public void onTransitionEnd(Transition transition) {
setUpArtistImageAndApplyPalette(false);
}
@Override
public void onTransitionCancel(Transition transition) {
}
@Override
public void onTransitionPause(Transition transition) {
}
@Override
public void onTransitionResume(Transition transition) {
}
});
}
}
@Override
public String getTag() {
return TAG;
}
@Override
public void goToArtist(int artistId) {
if (artist.id != artistId) {
super.goToArtist(artistId);
}
}
private void initViews() {
artistImageView = (ImageView) findViewById(R.id.artist_image);
toolbar = (Toolbar) findViewById(R.id.toolbar);
artistArtOverlayView = findViewById(R.id.overlay);
artistTitleText = (TextView) findViewById(R.id.artist_name);
absAlbumListBackgroundView = findViewById(R.id.list_background);
statusBar = findViewById(R.id.statusBar);
slidingTabs = (SlidingTabLayout) findViewById(R.id.sliding_tabs);
}
private void setUpObservableListViewParams() {
artistImageViewHeight = getResources().getDimensionPixelSize(R.dimen.header_image_height);
toolbarColor = Util.resolveColor(this, R.attr.colorPrimary);
toolbarHeight = Util.getActionBarSize(this);
titleViewHeight = getResources().getDimensionPixelSize(R.dimen.title_view_height);
headerOffset = toolbarHeight;
headerOffset += getResources().getDimensionPixelSize(R.dimen.statusMargin);
tabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height);
}
private void setUpViews() {
artistTitleText.setText(artist.name);
ViewHelper.setAlpha(artistArtOverlayView, 0);
setUpArtistImageAndApplyPalette(false);
setUpViewPatch();
setUpSlidingTabs();
}
private void setUpSlidingTabs() {
navigationAdapter = new NavigationAdapter(this, artist);
viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setOffscreenPageLimit(2);
viewPager.setAdapter(navigationAdapter);
viewPager.setCurrentItem(1);
slidingTabs.setViewPager(viewPager);
slidingTabs.setDistributeEvenly(true);
slidingTabs.setCustomTabView(R.layout.tab_indicator, android.R.id.text1);
slidingTabs.setSelectedIndicatorColors(Util.resolveColor(this, R.attr.colorAccent));
slidingTabs.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
currentFragment = navigationAdapter.getItemAt(position);
if (currentFragment instanceof AbsViewPagerTabArtistListFragment) {
restoreY(((AbsViewPagerTabArtistListFragment) currentFragment).getY());
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
public void restoreY(final int scrollY) {
translateToolBar(scrollY);
int animationTime = 1000;
DecelerateInterpolator interpolator = new DecelerateInterpolator(4);
int titleTranslationY = getTitleTranslation(scrollY);
ViewPropertyAnimator.animate(artistArtOverlayView).y(getOverlayTranslation(scrollY)).setDuration(animationTime).setInterpolator(interpolator).start();
ViewPropertyAnimator.animate(artistImageView).y(getImageViewTranslation(scrollY)).setDuration(animationTime).setInterpolator(interpolator).start();
ViewPropertyAnimator.animate(absAlbumListBackgroundView).y(getListBackgroundTranslation(scrollY)).setDuration(animationTime).setInterpolator(interpolator).start();
ViewPropertyAnimator.animate(artistArtOverlayView).alpha(getOverlayAlpha(scrollY)).setDuration(animationTime).setInterpolator(interpolator).start();
ViewPropertyAnimator.animate(slidingTabs).y(titleTranslationY + titleViewHeight).setDuration(animationTime).setInterpolator(interpolator).start();
ViewPropertyAnimator.animate(artistTitleText).y(titleTranslationY).setDuration(animationTime).setInterpolator(interpolator).start();
ViewPropertyAnimator.animate(getFab()).y(getFabTranslation(scrollY)).setDuration(animationTime).setInterpolator(interpolator).setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
isAnimating = true;
}
@Override
public void onAnimationEnd(Animator animation) {
translateToolBar(scrollY);
isAnimating = false;
if (currentFragment instanceof AbsViewPagerTabArtistListFragment) {
onScrollChanged((((AbsViewPagerTabArtistListFragment) currentFragment).getY()), false, false);
}
}
@Override
public void onAnimationCancel(Animator animation) {
isAnimating = false;
}
@Override
public void onAnimationRepeat(Animator animation) {
isAnimating = true;
}
}).start();
}
@Override
public void onScrollChanged(int scrollY, boolean b, boolean b2) {
if (!isAnimating) {
int titleTranslationY = getTitleTranslation(scrollY);
ViewHelper.setTranslationY(artistArtOverlayView, getOverlayTranslation(scrollY));
ViewHelper.setTranslationY(artistImageView, getImageViewTranslation(scrollY));
ViewHelper.setTranslationY(absAlbumListBackgroundView, getListBackgroundTranslation(scrollY));
ViewHelper.setAlpha(artistArtOverlayView, getOverlayAlpha(scrollY));
ViewHelper.setTranslationY(artistTitleText, titleTranslationY);
ViewHelper.setTranslationY(slidingTabs, titleTranslationY);
ViewHelper.setTranslationY(getFab(), getFabTranslation(scrollY));
translateToolBar(scrollY);
}
}
@Override
public void onDownMotionEvent() {
}
@Override
public void onUpOrCancelMotionEvent(ScrollState scrollState) {
}
private int getImageViewTranslation(int scrollY) {
int minOverlayTransitionY = headerOffset - artistArtOverlayView.getHeight();
return Math.max(minOverlayTransitionY, Math.min(0, -scrollY / 2));
}
private int getOverlayTranslation(int scrollY) {
int minOverlayTransitionY = headerOffset - artistArtOverlayView.getHeight();
return Math.max(minOverlayTransitionY, Math.min(0, -scrollY));
}
private int getListBackgroundTranslation(int scrollY) {
return Math.max(0, -scrollY + artistImageViewHeight - 200);
}
private int getTitleTranslation(int scrollY) {
int maxTitleTranslationY = artistImageViewHeight;
int titleTranslationY = maxTitleTranslationY - scrollY;
if (TOOLBAR_IS_STICKY) {
titleTranslationY = Math.max(headerOffset, titleTranslationY);
}
return titleTranslationY;
}
private int getFabTranslation(int scrollY) {
return getTitleTranslation(scrollY) + titleViewHeight + tabHeight - (getFab().getHeight() / 2);
}
private float getOverlayAlpha(int scrollY) {
float flexibleRange = artistImageViewHeight - headerOffset;
return Math.max(0, Math.min(1, (float) scrollY / flexibleRange));
}
private void translateToolBar(int scrollY) {
if (TOOLBAR_IS_STICKY) {
// Change alpha of toolbar background
if (-scrollY + artistImageViewHeight <= headerOffset) {
ViewUtil.setBackgroundAlpha(toolbar, 1, toolbarColor);
ViewUtil.setBackgroundAlpha(statusBar, 1, toolbarColor);
} else {
ViewUtil.setBackgroundAlpha(toolbar, 0, toolbarColor);
ViewUtil.setBackgroundAlpha(statusBar, 0, toolbarColor);
}
} else {
// Translate Toolbar
if (scrollY < artistImageViewHeight) {
ViewHelper.setTranslationY(toolbar, 0);
} else {
ViewHelper.setTranslationY(toolbar, -scrollY);
}
}
}
private void setUpArtistImageAndApplyPalette(final boolean forceDownload) {
LastFMArtistImageLoader.loadArtistImage(this, artist.name, forceDownload, new LastFMArtistImageLoader.ArtistImageLoaderCallback() {
@SuppressLint("NewApi")
@Override
public void onArtistImageLoaded(Bitmap artistImage) {
if (artistImage != null) {
artistImageView.setImageBitmap(artistImage);
applyPalette(artistImage);
}
if(forceDownload){
Toast.makeText(ArtistDetailActivity.this, getResources().getString(R.string.updated_artist_image_for) + " " + artist.name, Toast.LENGTH_SHORT).show();
}
}
});
}
private void applyPalette(Bitmap bitmap) {
Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
Palette.Swatch swatch = palette.getVibrantSwatch();
if (swatch != null) {
toolbarColor = swatch.getRgb();
artistArtOverlayView.setBackgroundColor(swatch.getRgb());
artistTitleText.setBackgroundColor(swatch.getRgb());
slidingTabs.setBackgroundColor(swatch.getRgb());
artistTitleText.setTextColor(swatch.getTitleTextColor());
} else {
setStandardColors();
}
}
});
}
private void setStandardColors() {
int titleTextColor = Util.resolveColor(this, R.attr.title_text_color);
int defaultBarColor = getResources().getColor(R.color.materialmusic_default_bar_color);
toolbarColor = defaultBarColor;
artistArtOverlayView.setBackgroundColor(defaultBarColor);
artistTitleText.setBackgroundColor(defaultBarColor);
slidingTabs.setBackgroundColor(defaultBarColor);
artistTitleText.setTextColor(titleTextColor);
}
private void setUpViewPatch() {
final View contentView = getWindow().getDecorView().findViewById(android.R.id.content);
contentView.post(new Runnable() {
@Override
public void run() {
absAlbumListBackgroundView.getLayoutParams().height = contentView.getHeight();
}
});
}
private void setUpToolBar() {
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(null);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (!TOOLBAR_IS_STICKY) {
toolbar.setBackgroundColor(Color.TRANSPARENT);
}
}
private void getIntentExtras() {
Bundle intentExtras = getIntent().getExtras();
final int artistId = intentExtras.getInt(AppKeys.E_ARTIST);
artist = ArtistLoader.getArtist(this, artistId);
if (artist == null) {
finish();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_artist_detail, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home:
super.onBackPressed();
return true;
case R.id.action_re_download_artist_image:
Toast.makeText(ArtistDetailActivity.this, getResources().getString(R.string.updating), Toast.LENGTH_SHORT).show();
setUpArtistImageAndApplyPalette(true);
case R.id.action_settings:
return true;
case R.id.action_current_playing:
openCurrentPlayingIfPossible(null);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void enableViews() {
super.enableViews();
viewPager.setEnabled(true);
toolbar.setEnabled(true);
}
@Override
public void disableViews() {
super.disableViews();
viewPager.setEnabled(false);
toolbar.setEnabled(false);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
}
private static class NavigationAdapter extends FragmentPagerAdapter {
private String[] titles;
private SparseArray<Fragment> pages; //TODO check if this must be static
private Artist artist;
private Context context;
public NavigationAdapter(Activity activity, Artist artist) {
super(activity.getFragmentManager());
this.artist = artist;
pages = new SparseArray<>();
context = activity;
titles = new String[]{
context.getResources().getString(R.string.tab_songs),
context.getResources().getString(R.string.tab_albums),
context.getResources().getString(R.string.tab_biography)
};
}
@Override
public Fragment getItem(int position) {
final Bundle args = new Bundle();
args.putInt(ARG_ARTIST_ID, artist.id);
args.putString(ARG_ARTIST_NAME, artist.name);
Fragment f = getOrCreateFragmentAt(position);
f.setArguments(args);
pages.put(position, f);
return f;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (0 <= pages.indexOfKey(position)) {
pages.remove(position);
}
super.destroyItem(container, position, object);
}
private Fragment getOrCreateFragmentAt(int position) {
switch (position) {
case 1:
return pages.get(position, new ViewPagerTabArtistAlbumFragment());
case 0:
return pages.get(position, new ViewPagerTabArtistSongListFragment());
case 2:
return pages.get(position, new ViewPagerTabArtistBioFragment());
default:
return pages.get(position, new MainActivity.PlaceholderFragment());
}
}
public Fragment getItemAt(int position) {
return pages.get(position, null);
}
@Override
public int getCount() {
return titles.length;
}
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
}
}

View file

@ -0,0 +1,354 @@
package com.kabouzeid.gramophone.ui.activities;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.google.samples.apps.iosched.ui.widget.SlidingTabLayout;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.helper.AboutDeveloperDialogHelper;
import com.kabouzeid.gramophone.helper.PlayingQueueDialogHelper;
import com.kabouzeid.gramophone.interfaces.KabViewsDisableAble;
import com.kabouzeid.gramophone.interfaces.OnMusicRemoteEventListener;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.MusicRemoteEvent;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.base.AbsFabActivity;
import com.kabouzeid.gramophone.ui.fragments.NavigationDrawerFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivityfragments.AlbumViewFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivityfragments.ArtistViewFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivityfragments.MainActivityFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivityfragments.SongViewFragment;
import com.kabouzeid.gramophone.util.ImageLoaderUtil;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.Util;
import com.kabouzeid.gramophone.util.ViewUtil;
import com.nostra13.universalimageloader.core.ImageLoader;
public class MainActivity extends AbsFabActivity
implements NavigationDrawerFragment.NavigationDrawerCallbacks, OnMusicRemoteEventListener, KabViewsDisableAble {
public static final String TAG = MainActivity.class.getSimpleName();
private DrawerLayout drawerLayout;
private ActionBarDrawerToggle drawerToggle;
private NavigationDrawerFragment navigationDrawerFragment;
private Toolbar toolbar;
private View statusBar;
private MainActivityViewPagerAdapter viewPagerAdapter;
private ViewPager viewPager;
private SlidingTabLayout slidingTabLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
setUpTranslucence(true, true);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
navigationDrawerFragment.setUp(
R.id.navigation_drawer,
drawerLayout
);
setUpToolBar();
setUpViewPager();
}
private void setUpViewPager() {
viewPagerAdapter = new MainActivityViewPagerAdapter(this);
viewPager.setAdapter(viewPagerAdapter);
int startPosition = getApp().getDefaultSharedPreferences().getInt(AppKeys.SP_VIEWPAGER_ITEM_POSITION, 1);
viewPager.setCurrentItem(startPosition);
navigationDrawerFragment.setItemChecked(startPosition);
slidingTabLayout.setSelectedIndicatorColors(Util.resolveColor(MainActivity.this, R.attr.colorAccent));
slidingTabLayout.setViewPager(viewPager);
slidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(final int position) {
getApp().getDefaultSharedPreferences().edit().putInt(AppKeys.SP_VIEWPAGER_ITEM_POSITION, position).apply();
navigationDrawerFragment.setItemChecked(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
private void initViews() {
viewPager = (ViewPager) findViewById(R.id.pager);
slidingTabLayout = (SlidingTabLayout) findViewById(R.id.sliding_tabs);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
navigationDrawerFragment = (NavigationDrawerFragment)
getFragmentManager().findFragmentById(R.id.navigation_drawer);
}
private void setUpToolBar() {
setTitle(getResources().getString(R.string.app_name));
toolbar = (Toolbar) findViewById(R.id.toolbar);
statusBar = findViewById(R.id.statusBar);
setSupportActionBar(toolbar);
float alpha = 0.97f;
ViewUtil.setBackgroundAlpha(toolbar, alpha, Util.resolveColor(this, R.attr.colorPrimary));
ViewUtil.setBackgroundAlpha(statusBar, alpha, Util.resolveColor(this, R.attr.colorPrimary));
ViewUtil.setBackgroundAlpha(slidingTabLayout, alpha, Util.resolveColor(this, R.attr.colorPrimary));
setUpDrawerToggle();
}
private void setUpDrawerToggle() {
drawerToggle = new ActionBarDrawerToggle(
this,
drawerLayout,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
);
drawerLayout.post(new Runnable() {
@Override
public void run() {
drawerToggle.syncState();
}
});
drawerLayout.setDrawerListener(drawerToggle);
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onResume() {
super.onResume();
updateNavigationDrawerHeader();
}
private void updateNavigationDrawerHeader() {
if (navigationDrawerFragment != null) {
Song song = getApp().getMusicPlayerRemote().getCurrentSong();
if (song.id != -1) {
ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(song.albumId).toString(), navigationDrawerFragment.getAlbumArtImageView(), new ImageLoaderUtil.defaultAlbumArtOnFailed());
navigationDrawerFragment.getSongTitle().setText(song.title);
navigationDrawerFragment.getSongArtist().setText(song.artistName);
}
}
}
@Override
public void enableViews() {
try {
super.enableViews();
toolbar.setEnabled(true);
((MainActivityFragment)viewPagerAdapter.getItem(viewPager.getCurrentItem())).enableViews();
} catch (NullPointerException e) {
Log.e(TAG, "wasn't able to enable the views", e);
}
}
@Override
public void disableViews() {
try {
super.disableViews();
((MainActivityFragment)viewPagerAdapter.getItem(viewPager.getCurrentItem())).disableViews();
} catch (NullPointerException e) {
Log.e(TAG, "wasn't able to disable the views", e);
}
}
@Override
public void onMusicRemoteEvent(MusicRemoteEvent event) {
super.onMusicRemoteEvent(event);
if (event.getAction() == MusicRemoteEvent.STATE_RESTORED || event.getAction() == MusicRemoteEvent.TRACK_CHANGED) {
updateNavigationDrawerHeader();
}
}
@Override
public void onNavigationDrawerItemSelected(int position) {
if (position == NavigationDrawerFragment.NAVIGATION_DRAWER_HEADER) {
openCurrentPlayingIfPossible(null);
} else {
if (viewPager != null) {
viewPager.setCurrentItem(position, true);
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.drawer, menu);
restoreActionBar();
return true;
}
public void restoreActionBar() {
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
int id = item.getItemId();
switch (id) {
case R.id.action_search:
startActivity(new Intent(MainActivity.this, SearchActivity.class));
return true;
case R.id.action_settings:
return true;
case R.id.action_about:
AboutDeveloperDialogHelper.getDialog(this).show();
return true;
case R.id.action_current_playing:
openCurrentPlayingIfPossible(null);
return true;
case R.id.action_playing_queue:
final MaterialDialog materialDialog = PlayingQueueDialogHelper.getDialog(this, this);
if (materialDialog != null) {
materialDialog.show();
} else {
Toast.makeText(this, getResources().getString(R.string.nothing_playing), Toast.LENGTH_SHORT).show();
}
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
drawerToggle.onConfigurationChanged(newConfig);
super.onConfigurationChanged(newConfig);
}
@Override
protected void onDestroy() {
super.onDestroy();
getApp().getMusicPlayerRemote().removeAllOnMusicRemoteEventListeners();
}
@Override
public void onBackPressed() {
if (navigationDrawerFragment.isDrawerOpen()) {
drawerLayout.closeDrawers();
return;
}
super.onBackPressed();
}
public static class PlaceholderFragment extends MainActivityFragment {
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_place_holder, container, false);
TextView text = (TextView) rootView.findViewById(R.id.text);
text.setText("Coming soon!");
return rootView;
}
@Override
public void search(String query) {
}
@Override
public void returnToNonSearch() {
}
@Override
public void enableViews() {
}
@Override
public void disableViews() {
}
@Override
public boolean areViewsEnabled() {
return false;
}
}
private class MainActivityViewPagerAdapter extends FragmentPagerAdapter {
private String[] titles;
private SparseArray<MainActivityFragment> pages; //TODO check if this must be static
private Context context;
public MainActivityViewPagerAdapter(Activity activity) {
super(activity.getFragmentManager());
pages = new SparseArray<>();
context = activity;
titles = new String[]{
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)
};
}
@Override
public Fragment getItem(final int position) {
switch (position) {
case 0:
return pages.get(position, new SongViewFragment());
case 1:
return pages.get(position, new AlbumViewFragment());
case 2:
return pages.get(position, new ArtistViewFragment());
case 3:
//TODO genres
case 4:
//TODO playlists
}
return pages.get(position, new PlaceholderFragment());
}
@Override
public int getCount() {
return titles.length;
}
@Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
}
}

View file

@ -0,0 +1,440 @@
package com.kabouzeid.gramophone.ui.activities;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.util.Pair;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.helper.PlayingQueueDialogHelper;
import com.kabouzeid.gramophone.helper.SongDetailDialogHelper;
import com.kabouzeid.gramophone.interfaces.OnMusicRemoteEventListener;
import com.kabouzeid.gramophone.lastfm.artist.LastFMArtistImageLoader;
import com.kabouzeid.gramophone.loader.SongFilePathLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.MusicRemoteEvent;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.service.MusicService;
import com.kabouzeid.gramophone.ui.activities.base.AbsFabActivity;
import com.kabouzeid.gramophone.ui.activities.tageditor.SongTagEditorActivity;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.Util;
import com.kabouzeid.gramophone.util.ViewUtil;
import com.nineoldandroids.view.ViewPropertyAnimator;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import java.io.File;
public class MusicControllerActivity extends AbsFabActivity implements OnMusicRemoteEventListener {
public static final String TAG = MusicControllerActivity.class.getSimpleName();
private static final int DEFAULT_DELAY = 350;
private static final int DEFAULT_ANIMATION_TIME = 1000;
private Song song;
private ImageView albumArt;
private ImageView artistArt;
private TextView songTitle;
private TextView songArtist;
private TextView currentSongProgress;
private TextView totalSongDuration;
private View footer;
private SeekBar progressSlider;
private ImageButton nextButton;
private ImageButton prevButton;
private ImageButton repeatButton;
private ImageButton shuffleButton;
private int lastFooterColor = -1;
private boolean killThreads = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
setUpTranslucence(true, false);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_music_controller);
initViews();
moveSeekBarIntoPlace();
setUpMusicControllers();
prepareViewsForOpenAnimation();
setUpToolBar();
}
private void moveSeekBarIntoPlace() {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) progressSlider.getLayoutParams();
progressSlider.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
lp.setMargins(0, 0, 0, -(progressSlider.getMeasuredHeight() / 2));
progressSlider.setLayoutParams(lp);
}
private void initViews() {
nextButton = (ImageButton) findViewById(R.id.next_button);
prevButton = (ImageButton) findViewById(R.id.prev_button);
repeatButton = (ImageButton) findViewById(R.id.repeat_button);
shuffleButton = (ImageButton) findViewById(R.id.shuffle_button);
albumArt = (ImageView) findViewById(R.id.album_art);
artistArt = (ImageView) findViewById(R.id.artist_image);
songTitle = (TextView) findViewById(R.id.song_title);
songArtist = (TextView) findViewById(R.id.song_artist);
currentSongProgress = (TextView) findViewById(R.id.song_current_progress);
totalSongDuration = (TextView) findViewById(R.id.song_total_time);
footer = findViewById(R.id.footer);
progressSlider = (SeekBar) findViewById(R.id.progress_slider);
}
private void setUpMusicControllers() {
setUpPrevNext();
setUpRepeatButton();
setUpShuffleButton();
setUpProgressSlider();
}
private void setUpProgressSlider() {
progressSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
getApp().getMusicPlayerRemote().seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
private void setUpPrevNext() {
nextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getApp().getMusicPlayerRemote().playNextSong();
}
});
prevButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getApp().getMusicPlayerRemote().back();
}
});
}
private void setUpShuffleButton() {
updateShuffleState();
shuffleButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getApp().getMusicPlayerRemote().toggleShuffleMode();
}
});
}
private void updateShuffleState() {
switch (getApp().getMusicPlayerRemote().getShuffleMode()) {
case MusicService.SHUFFLE_MODE_SHUFFLE:
shuffleButton.setImageResource(R.drawable.ic_shuffle_white_48dp);
break;
default:
shuffleButton.setImageResource(R.drawable.ic_shuffle_grey600_48dp);
break;
}
}
private void setUpRepeatButton() {
updateRepeatState();
repeatButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getApp().getMusicPlayerRemote().cycleRepeatMode();
}
});
}
private void updateRepeatState() {
switch (getApp().getMusicPlayerRemote().getRepeatMode()) {
case MusicService.REPEAT_MODE_NONE:
repeatButton.setImageResource(R.drawable.ic_repeat_grey600_48dp);
break;
case MusicService.REPEAT_MODE_ALL:
repeatButton.setImageResource(R.drawable.ic_repeat_white_48dp);
break;
default:
repeatButton.setImageResource(R.drawable.ic_repeat_one_white_48dp);
break;
}
}
private void prepareViewsForOpenAnimation() {
footer.setPivotY(0);
footer.setScaleY(0);
}
private void setUpToolBar() {
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
getSupportActionBar().setTitle(null);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public String getTag() {
return TAG;
}
@Override
protected void onResume() {
super.onResume();
startMusicControllerStateUpdateThread();
updateCurrentSong();
}
private void updateCurrentSong() {
getCurrentSong();
setHeadersText();
setUpArtistArt();
setUpAlbumArtAndApplyPalette();
totalSongDuration.setText(MusicUtil.getReadableDurationString(song.duration));
currentSongProgress.setText(MusicUtil.getReadableDurationString(-1));
}
private void setHeadersText() {
songTitle.setText(song.title);
songArtist.setText(song.artistName);
}
private void setUpAlbumArtAndApplyPalette() {
ImageLoader.getInstance().displayImage(MusicUtil.getAlbumArtUri(song.albumId).toString(), albumArt, new ImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
albumArt.setImageResource(R.drawable.default_album_art);
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
albumArt.setImageResource(R.drawable.default_album_art);
setStandardColors();
}
@Override
public void onLoadingComplete(String imageUri, final View view, Bitmap loadedImage) {
applyPalette(loadedImage);
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
albumArt.setImageResource(R.drawable.default_album_art);
setStandardColors();
}
});
}
private void applyPalette(Bitmap bitmap) {
Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
Palette.Swatch swatch = palette.getVibrantSwatch();
if (swatch != null) {
animateColorChange(swatch.getRgb());
songTitle.setTextColor(swatch.getTitleTextColor());
songArtist.setTextColor(swatch.getBodyTextColor());
} else {
setStandardColors();
}
}
});
}
private void setStandardColors() {
int songTitleTextColor = Util.resolveColor(this, R.attr.title_text_color);
int artistNameTextColor = Util.resolveColor(this, R.attr.caption_text_color);
int defaultBarColor = getResources().getColor(R.color.materialmusic_default_bar_color);
animateColorChange(defaultBarColor);
songTitle.setTextColor(songTitleTextColor);
songArtist.setTextColor(artistNameTextColor);
}
private void animateColorChange(final int newColor) {
if (lastFooterColor != -1 && lastFooterColor != newColor) {
ViewUtil.animateViewColor(footer, lastFooterColor, newColor, 300);
} else {
footer.setBackgroundColor(newColor);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setNavigationBarColor(newColor);
}
lastFooterColor = newColor;
}
private void setUpArtistArt() {
if (artistArt != null) {
artistArt.setImageResource(R.drawable.default_artist_image);
LastFMArtistImageLoader.loadArtistImage(this, song.artistName, new LastFMArtistImageLoader.ArtistImageLoaderCallback() {
@Override
public void onArtistImageLoaded(Bitmap artistImage) {
artistArt.setImageBitmap(artistImage);
}
});
}
}
private void getCurrentSong() {
song = getApp().getMusicPlayerRemote().getCurrentSong();
if (song.id == -1) {
finish();
}
}
private void startMusicControllerStateUpdateThread() {
killThreads = false;
new Thread(new Runnable() {
@Override
public void run() {
int currentPosition = 0;
int total = 0;
while (getApp().getMusicPlayerRemote().isMusicBound() && !killThreads) {
try {
total = getApp().getMusicPlayerRemote().getSongDurationMillis();
currentPosition = getApp().getMusicPlayerRemote().getSongProgressMillis();
Thread.sleep(1);
} catch (InterruptedException e) {
return;
} catch (Exception e) {
}
final int finalTotal = total;
final int finalCurrentPosition = currentPosition;
runOnUiThread(new Runnable() {
@Override
public void run() {
progressSlider.setMax(finalTotal);
progressSlider.setProgress(finalCurrentPosition);
currentSongProgress.setText(MusicUtil.getReadableDurationString(finalCurrentPosition));
}
});
}
}
}).start();
}
@Override
protected boolean openCurrentPlayingIfPossible(Pair[] sharedViews) {
onBackPressed();
return true;
}
@Override
protected void updateControllerState() {
super.updateControllerState();
updateRepeatState();
updateShuffleState();
}
@Override
public void onMusicRemoteEvent(MusicRemoteEvent event) {
super.onMusicRemoteEvent(event);
switch (event.getAction()) {
case MusicRemoteEvent.TRACK_CHANGED:
updateCurrentSong();
break;
case MusicRemoteEvent.REPEAT_MODE_CHANGED:
updateRepeatState();
break;
case MusicRemoteEvent.SHUFFLE_MODE_CHANGED:
updateShuffleState();
break;
}
}
@Override
protected void onPause() {
super.onPause();
killThreads = true;
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
animateActivityOpened(DEFAULT_DELAY);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_title_playing, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home:
super.onBackPressed();
return true;
case R.id.action_playing_queue:
final MaterialDialog materialDialog = PlayingQueueDialogHelper.getDialog(this, this);
if (materialDialog != null) {
materialDialog.show();
} else {
Toast.makeText(this, getResources().getString(R.string.nothing_playing), Toast.LENGTH_SHORT).show();
}
return true;
case R.id.action_tag_editor:
Intent intent = new Intent(this, SongTagEditorActivity.class);
intent.putExtra(AppKeys.E_ID, song.id);
startActivity(intent);
return true;
case R.id.action_details:
String songFilePath = SongFilePathLoader.getSongFilePath(this, song.id);
File songFile = new File(songFilePath);
SongDetailDialogHelper.getDialog(this, songFile).show();
return true;
case R.id.action_go_to_album:
goToAlbum(song.albumId, null);
return true;
case R.id.action_go_to_artist:
goToArtist(song.artistId, null);
return true;
}
return super.onOptionsItemSelected(item);
}
private void animateActivityOpened(int startDelay) {
ViewPropertyAnimator.animate(footer)
.scaleX(1)
.scaleY(1)
.setInterpolator(new DecelerateInterpolator(4))
.setDuration(DEFAULT_ANIMATION_TIME)
.setStartDelay(startDelay)
.start();
}
}

View file

@ -0,0 +1,222 @@
package com.kabouzeid.gramophone.ui.activities;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.util.Pair;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.SearchAdapter;
import com.kabouzeid.gramophone.loader.AlbumLoader;
import com.kabouzeid.gramophone.loader.ArtistLoader;
import com.kabouzeid.gramophone.loader.SongLoader;
import com.kabouzeid.gramophone.model.Album;
import com.kabouzeid.gramophone.model.Artist;
import com.kabouzeid.gramophone.model.SearchEntry;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.base.AbsBaseActivity;
import com.kabouzeid.gramophone.util.Util;
import java.util.ArrayList;
import java.util.List;
public class SearchActivity extends AbsBaseActivity {
public static final String TAG = SearchActivity.class.getSimpleName();
private ListView listView;
private SearchView searchView;
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
setUpTranslucence(false, false);
setTitle(null);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
if (Util.hasLollipopSDK()) {
getWindow().setStatusBarColor(Util.resolveColor(this, R.attr.colorPrimaryDark));
getWindow().setNavigationBarColor(Util.resolveColor(this, R.attr.colorPrimaryDark));
}
listView = (ListView) findViewById(R.id.list);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Object item = parent.getItemAtPosition(position);
if (item instanceof SearchEntry) {
if (item instanceof Song) {
List<Song> playList = new ArrayList<>();
playList.add((Song) item);
getApp().getMusicPlayerRemote().openQueue(playList, 0, true);
} else if (item instanceof Album) {
goToAlbum(((Album) item).id, new Pair[]{Pair.create(view.findViewById(R.id.image), getResources().getString(R.string.transition_album_cover))});
} else if (item instanceof Artist) {
goToArtist(((Artist) item).id, new Pair[]{Pair.create(view.findViewById(R.id.image), getResources().getString(R.string.transition_artist_image))});
}
}
}
});
listView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Util.hideSoftKeyboard(SearchActivity.this);
if(searchView != null){
searchView.clearFocus();
}
return false;
}
});
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public String getTag() {
return TAG;
}
@Override
public void enableViews() {
super.enableViews();
listView.setEnabled(true);
}
@Override
public void disableViews() {
super.disableViews();
listView.setEnabled(false);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_search, menu);
final MenuItem search = menu.findItem(R.id.action_search);
searchView = (SearchView) MenuItemCompat.getActionView(search);
searchView.setIconified(false);
searchView.setIconifiedByDefault(false);
ActionBar.LayoutParams params = new ActionBar.LayoutParams(ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.MATCH_PARENT);
searchView.setLayoutParams(params);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
onQueryTextChange(query);
Util.hideSoftKeyboard(SearchActivity.this);
searchView.clearFocus();
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
search(newText);
return false;
}
});
MenuItemCompat.setOnActionExpandListener(search, new MenuItemCompat.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
finish();
return false;
}
});
MenuItemCompat.expandActionView(search);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private void search(String query) {
List<SearchEntry> results = new ArrayList<>();
if (!query.trim().equals("")) {
LabelEntry songLabel = new LabelEntry(getResources().getString(R.string.songs).toUpperCase());
results.add(songLabel);
List<Song> songs = SongLoader.getSongs(this, query);
results.addAll(songs);
songLabel.setNumber(songs.size());
LabelEntry artistLabel = new LabelEntry(getResources().getString(R.string.artists).toUpperCase());
results.add(artistLabel);
List<Artist> artists = ArtistLoader.getArtists(this, query);
results.addAll(artists);
artistLabel.setNumber(artists.size());
LabelEntry albumLabel = new LabelEntry(getResources().getString(R.string.albums).toUpperCase());
results.add(albumLabel);
List<Album> albums = AlbumLoader.getAlbums(this, query);
results.addAll(albums);
albumLabel.setNumber(albums.size());
}
if(results.size() <= 3){
results.clear();
results.add(new LabelEntry(getResources().getString(R.string.no_results).toUpperCase()));
}
ArrayAdapter adapter = new SearchAdapter(this, results);
listView.setAdapter(adapter);
}
public static class LabelEntry implements SearchEntry {
String title;
String label;
public LabelEntry(String label) {
this.label = label;
this.title = label;
}
public void setNumber(int number){
if(number != -1) {
label = title + " (" + number + ")";
} else {
label = title;
}
}
@Override
public String getTitle() {
return label;
}
@Override
public String getSubTitle() {
return "";
}
@Override
public void loadImage(Context context, ImageView imageView) {
}
}
}

View file

@ -0,0 +1,138 @@
package com.kabouzeid.gramophone.ui.activities.base;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.util.Pair;
import android.support.v7.app.ActionBarActivity;
import android.widget.Toast;
import com.crashlytics.android.Crashlytics;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.songadapter.SongAdapter;
import com.kabouzeid.gramophone.interfaces.KabViewsDisableAble;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.ui.activities.AlbumDetailActivity;
import com.kabouzeid.gramophone.ui.activities.ArtistDetailActivity;
import com.kabouzeid.gramophone.ui.activities.MusicControllerActivity;
import com.kabouzeid.gramophone.util.Util;
/**
* Created by karim on 20.01.15.
*/
public abstract class AbsBaseActivity extends ActionBarActivity implements KabViewsDisableAble, SongAdapter.GoToAble {
private App app;
private boolean areViewsEnabled;
@Override
protected void onCreate(Bundle savedInstanceState) {
Crashlytics.setString(AppKeys.CL_CURRENT_ACTIVITY, getTag());
setTheme(getApp().getAppTheme());
super.onCreate(savedInstanceState);
}
protected App getApp() {
if (app == null) {
app = (App) getApplicationContext();
}
return app;
}
public abstract String getTag();
@Override
protected void onResume() {
super.onResume();
enableViews();
}
@Override
public void enableViews() {
areViewsEnabled = true;
}
@Override
public void disableViews() {
areViewsEnabled = false;
}
@Override
public boolean areViewsEnabled() {
return areViewsEnabled;
}
protected void setUpTranslucence(boolean statusBarTranslucent, boolean navigationBarTranslucent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Util.setStatusBarTranslucent(getWindow(), statusBarTranslucent);
if (getApp().isInPortraitMode() || getApp().isTablet()) {
Util.setNavBarTranslucent(getWindow(), navigationBarTranslucent);
}
}
}
protected boolean openCurrentPlayingIfPossible(Pair[] sharedViews) {
if (getApp().getMusicPlayerRemote().getPosition() != -1) {
if (areViewsEnabled()) {
disableViews();
Intent intent = new Intent(this, MusicControllerActivity.class);
if (sharedViews != null) {
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this,
sharedViews
);
ActivityCompat.startActivity(this, intent, optionsCompat.toBundle());
} else {
startActivity(intent);
}
return true;
}
} else {
Toast.makeText(this, getResources().getString(R.string.nothing_playing), Toast.LENGTH_SHORT).show();
}
return false;
}
@Override
public void goToAlbum(int albumId) {
goToAlbum(albumId, null);
}
@Override
public void goToArtist(int artistId) {
goToArtist(artistId, null);
}
public void goToArtist(int artistId, Pair[] sharedViews) {
if(areViewsEnabled()) {
disableViews();
final Intent intent = new Intent(this, ArtistDetailActivity.class);
intent.putExtra(AppKeys.E_ARTIST, artistId);
if (sharedViews != null) {
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this,
sharedViews
);
ActivityCompat.startActivity(this, intent, optionsCompat.toBundle());
} else {
startActivity(intent);
}
}
}
public void goToAlbum(int albumId, Pair[] sharedViews) {
if(areViewsEnabled()) {
disableViews();
final Intent intent = new Intent(this, AlbumDetailActivity.class);
intent.putExtra(AppKeys.E_ALBUM, albumId);
if (sharedViews != null) {
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this,
sharedViews
);
ActivityCompat.startActivity(this, intent, optionsCompat.toBundle());
} else {
startActivity(intent);
}
}
}
}

View file

@ -0,0 +1,168 @@
package com.kabouzeid.gramophone.ui.activities.base;
import android.os.Bundle;
import android.support.v4.util.Pair;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.interfaces.OnMusicRemoteEventListener;
import com.kabouzeid.gramophone.misc.SmallOnGestureListener;
import com.kabouzeid.gramophone.model.MusicRemoteEvent;
import com.melnykov.fab.FloatingActionButton;
/**
* Created by karim on 22.01.15.
*/
public abstract class AbsFabActivity extends AbsBaseActivity implements OnMusicRemoteEventListener {
private FloatingActionButton fab;
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
setUpFab();
}
private void setUpFab() {
updateFabState();
final GestureDetector gestureDetector = new GestureDetector(this, new SmallOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
openCurrentPlayingIfPossible(null);
return true;
}
});
getFab().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getApp().getMusicPlayerRemote().getPosition() != -1) {
if (getApp().getMusicPlayerRemote().isPlaying()) {
getApp().getMusicPlayerRemote().pauseSong();
} else {
getApp().getMusicPlayerRemote().resumePlaying();
}
} else {
Toast.makeText(AbsFabActivity.this, getResources().getString(R.string.nothing_playing), Toast.LENGTH_SHORT).show();
}
}
});
getFab().setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(AbsFabActivity.this, getResources().getString(R.string.hint_fling_to_open), Toast.LENGTH_SHORT).show();
return true;
}
});
getFab().setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
gestureDetector.onTouchEvent(event);
return false;
}
});
}
private void updateFabState() {
if (getApp().getMusicPlayerRemote().isPlaying()) {
getFab().setImageResource(R.drawable.ic_pause_white_24dp);
} else {
getFab().setImageResource(R.drawable.ic_play_arrow_white_24dp);
}
}
protected FloatingActionButton getFab() {
if (fab == null) {
fab = (FloatingActionButton) findViewById(R.id.fab);
if (fab == null) {
fab = new FloatingActionButton(this);
Log.e(getTag(), "No FAB found created default FAB.");
}
}
return fab;
}
@Override
protected void onResume() {
super.onResume();
updateControllerState();
getApp().getMusicPlayerRemote().addOnMusicRemoteEventListener(this);
}
@Override
public void enableViews() {
super.enableViews();
getFab().setEnabled(true);
}
@Override
public void disableViews() {
super.disableViews();
getFab().setEnabled(false);
}
@Override
protected boolean openCurrentPlayingIfPossible(Pair[] sharedViews) {
return super.openCurrentPlayingIfPossible(getSharedViewsWithFab(sharedViews));
}
@Override
public void goToArtist(int artistId, Pair[] sharedViews) {
super.goToArtist(artistId, getSharedViewsWithFab(sharedViews));
}
@Override
public void goToAlbum(int albumId, Pair[] sharedViews) {
super.goToAlbum(albumId, getSharedViewsWithFab(sharedViews));
}
private Pair[] getSharedViewsWithFab(Pair[] sharedViews) {
Pair[] sharedViewsWithFab;
if (sharedViews != null) {
sharedViewsWithFab = new Pair[sharedViews.length + 1];
for (int i = 0; i < sharedViews.length; i++) {
sharedViewsWithFab[i] = sharedViews[i];
}
} else {
sharedViewsWithFab = new Pair[1];
}
sharedViewsWithFab[sharedViewsWithFab.length - 1] = Pair.create((View) getFab(), getString(R.string.transition_fab));
return sharedViewsWithFab;
}
protected void updateControllerState() {
updateFabState();
}
@Override
protected void onStop() {
super.onStop();
getApp().getMusicPlayerRemote().removeOnMusicRemoteEventListener(this);
}
@Override
public void onMusicRemoteEvent(MusicRemoteEvent event) {
switch (event.getAction()) {
case MusicRemoteEvent.PLAY:
getFab().setImageDrawable(getResources().getDrawable(R.drawable.ic_pause_white_24dp));
break;
case MusicRemoteEvent.PAUSE:
getFab().setImageDrawable(getResources().getDrawable(R.drawable.ic_play_arrow_white_24dp));
break;
case MusicRemoteEvent.RESUME:
getFab().setImageDrawable(getResources().getDrawable(R.drawable.ic_pause_white_24dp));
break;
case MusicRemoteEvent.STOP:
getFab().setImageDrawable(getResources().getDrawable(R.drawable.ic_play_arrow_white_24dp));
break;
case MusicRemoteEvent.QUEUE_COMPLETED:
getFab().setImageResource(R.drawable.ic_play_arrow_white_24dp);
break;
}
}
}

View file

@ -0,0 +1,497 @@
package com.kabouzeid.gramophone.ui.activities.tageditor;
import android.app.SearchManager;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import android.widget.ImageView;
import com.afollestad.materialdialogs.MaterialDialog;
import com.github.ksoichiro.android.observablescrollview.ObservableScrollView;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.misc.SmallObservableScrollViewCallbacks;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.Util;
import com.kabouzeid.gramophone.util.ViewUtil;
import com.melnykov.fab.FloatingActionButton;
import com.nineoldandroids.view.ViewHelper;
import com.nineoldandroids.view.ViewPropertyAnimator;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.utils.MemoryCacheUtils;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.CannotWriteException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.images.Artwork;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Created by karim on 18.01.15.
*/
public abstract class AbsTagEditorActivity extends ActionBarActivity {
public static final String TAG = AbsTagEditorActivity.class.getSimpleName();
private static final int REQUEST_CODE_SELECT_IMAGE = 1337;
private App app;
private int id;
private int headerVariableSpace;
private int paletteColorPrimary;
private boolean isInNoImageMode;
private FloatingActionButton fab;
private ObservableScrollView scrollView;
private Toolbar toolBar;
private ImageView image;
private View header;
private SmallObservableScrollViewCallbacks observableScrollViewCallbacks = new SmallObservableScrollViewCallbacks() {
@Override
public void onScrollChanged(int scrollY, boolean b, boolean b2) {
float alpha;
if (!isInNoImageMode) {
alpha = 1 - (float) Math.max(0, headerVariableSpace - scrollY) / headerVariableSpace;
} else {
ViewHelper.setTranslationY(header, scrollY);
alpha = 1;
}
ViewUtil.setBackgroundAlpha(toolBar, alpha, paletteColorPrimary);
ViewUtil.setBackgroundAlpha(header, alpha, paletteColorPrimary);
ViewHelper.setTranslationY(image, scrollY / 2);
}
};
private List<String> songPaths;
@Override
protected void onCreate(Bundle savedInstanceState) {
app = (App) getApplicationContext();
setTheme(app.getAppTheme());
setUpTranslucence();
super.onCreate(savedInstanceState);
setContentView(getContentViewResId());
getIntentExtras();
headerVariableSpace = getResources().getDimensionPixelSize(R.dimen.tagEditorHeaderVariableSpace);
songPaths = getSongPaths();
initViews();
setUpViews();
setUpToolBar();
}
private void initViews() {
fab = (FloatingActionButton) findViewById(R.id.fab);
scrollView = (ObservableScrollView) findViewById(R.id.observableScrollView);
toolBar = (Toolbar) findViewById(R.id.toolbar);
image = (ImageView) findViewById(R.id.image);
header = findViewById(R.id.header);
}
private void setUpViews() {
restoreStandardColors();
setUpScrollView();
setUpFab();
setUpImageView();
}
private void setUpScrollView() {
scrollView.setScrollViewCallbacks(observableScrollViewCallbacks);
scrollView.post(new Runnable() {
@Override
public void run() {
scrollView.scrollVerticallyTo(headerVariableSpace / 2);
}
});
}
private void setUpImageView() {
loadCurrentImage();
image.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new MaterialDialog.Builder(AbsTagEditorActivity.this)
.title("Update image")
.items(new CharSequence[]{"Download from LastFM", "Pick from internal storage", "Web search", "Delete"})
.itemsCallback(new MaterialDialog.ListCallback() {
@Override
public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) {
switch (which) {
case 0:
getImageFromLastFM();
break;
case 1:
startImagePicker();
break;
case 2:
searchImageOnWeb();
break;
case 3:
deleteImage();
break;
}
}
})
.build()
.show();
}
});
}
private void startImagePicker() {
Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
photoPickerIntent.setType("image/*");
startActivityForResult(photoPickerIntent, REQUEST_CODE_SELECT_IMAGE);
}
protected abstract void loadCurrentImage();
protected abstract void getImageFromLastFM();
protected abstract void searchImageOnWeb();
protected abstract void deleteImage();
protected void setUpFab() {
ViewHelper.setScaleX(fab, 0);
ViewHelper.setScaleY(fab, 0);
fab.setEnabled(false);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
save();
}
});
}
protected abstract void save();
private void restoreStandardColors() {
final int vibrantColor = Util.resolveColor(this, R.attr.colorPrimary);
paletteColorPrimary = vibrantColor;
observableScrollViewCallbacks.onScrollChanged(scrollView.getCurrentScrollY(), false, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(vibrantColor);
getWindow().setNavigationBarColor(vibrantColor);
}
}
private void setUpTranslucence() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Util.setStatusBarTranslucent(getWindow(), false);
Util.setNavBarTranslucent(getWindow(), false);
}
}
private void getIntentExtras() {
Bundle intentExtras = getIntent().getExtras();
if (intentExtras != null) {
id = intentExtras.getInt(AppKeys.E_ID);
}
}
protected void setUpToolBar() {
setSupportActionBar(toolBar);
getSupportActionBar().setTitle(getResources().getString(R.string.tag_editor));
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
protected abstract int getContentViewResId();
protected abstract List<String> getSongPaths();
protected void searchWebFor(List<String> strings) {
StringBuilder stringBuilder = new StringBuilder();
for (String string : strings) {
stringBuilder.append(string);
stringBuilder.append(" ");
}
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY, stringBuilder.toString());
startActivity(intent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_tag_editor, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home:
super.onBackPressed();
return true;
case R.id.action_settings:
return true;
}
return super.onOptionsItemSelected(item);
}
protected void setNoImageMode() {
isInNoImageMode = true;
image.setVisibility(View.GONE);
image.setEnabled(false);
scrollView.setPadding(0, Util.getActionBarSize(this), 0, 0);
observableScrollViewCallbacks.onScrollChanged(scrollView.getCurrentScrollY(), false, false);
}
protected void dataChanged() {
showFab();
}
private void showFab() {
ViewPropertyAnimator.animate(fab)
.setDuration(500)
.setInterpolator(new OvershootInterpolator())
.scaleX(1)
.scaleY(1)
.start();
fab.setEnabled(true);
}
protected void setImageRes(int resId) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);
setImageBitmap(bitmap);
}
protected void setImageBitmap(final Bitmap bitmap) {
if (bitmap != null) {
image.setImageBitmap(bitmap);
applyPalette(bitmap);
}
}
private void applyPalette(final Bitmap bitmap) {
if (bitmap != null) {
Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
final int vibrantColor = palette.getVibrantColor(getResources().getColor(R.color.materialmusic_default_bar_color));
paletteColorPrimary = vibrantColor;
observableScrollViewCallbacks.onScrollChanged(scrollView.getCurrentScrollY(), false, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(vibrantColor);
getWindow().setNavigationBarColor(vibrantColor);
}
}
});
} else {
restoreStandardColors();
}
}
protected void writeValuesToFiles(final Map<FieldKey, String> fieldKeyValueMap) {
writeValuesToFiles(fieldKeyValueMap, null, false);
}
protected void writeValuesToFiles(final Map<FieldKey, String> fieldKeyValueMap, final Artwork artwork, final boolean deleteArtwork) {
Util.hideSoftKeyboard(this);
final String writingFileStr = getResources().getString(R.string.writing_file_number);
final String savingStr = getResources().getString(R.string.saving_changes);
final MaterialDialog progressDialog = new MaterialDialog.Builder(AbsTagEditorActivity.this)
.title(savingStr)
.cancelable(false)
.progress(true, 0)
.build();
progressDialog.show();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < songPaths.size(); i++) {
String songPath = songPaths.get(i);
final int finalI = i;
runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.setContent(writingFileStr + " " + ((finalI + 1) + "/" + songPaths.size()));
}
});
try {
AudioFile audioFile = AudioFileIO.read(new File(songPath));
Tag tag = audioFile.getTagOrCreateAndSetDefault();
for (Map.Entry<FieldKey, String> entry : fieldKeyValueMap.entrySet()) {
try {
tag.setField(entry.getKey(), entry.getValue());
} catch (NumberFormatException e) {
tag.deleteField(entry.getKey());
}
}
if (deleteArtwork) {
tag.deleteArtworkField();
} else if (artwork != null) {
tag.deleteArtworkField();
tag.setField(artwork);
}
audioFile.commit();
} catch (CannotReadException | IOException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
Log.e(TAG, "Error while reading audio file.", e);
} catch (CannotWriteException e) {
Log.e(TAG, "Error while writing audio file.", e);
}
}
if (deleteArtwork) {
String imagePath = MusicUtil.getAlbumArtUri(getId()).toString();
ImageLoader.getInstance().getDiskCache().remove(imagePath);
MemoryCacheUtils.removeFromCache(imagePath, ImageLoader.getInstance().getMemoryCache());
MusicUtil.deleteAlbumArt(AbsTagEditorActivity.this, getId());
} else if (artwork != null) {
String imagePath = MusicUtil.getAlbumArtUri(getId()).toString();
MemoryCacheUtils.removeFromCache(imagePath, ImageLoader.getInstance().getMemoryCache());
ImageLoader.getInstance().getDiskCache().remove(imagePath);
}
progressDialog.dismiss();
rescanMedia();
restartApp();
}
}).start();
}
private void rescanMedia() {
String[] toBeScanned = new String[songPaths.size()];
toBeScanned = songPaths.toArray(toBeScanned);
MediaScannerConnection.scanFile(this, toBeScanned, null, null);
}
private void restartApp() {
Intent i = getBaseContext().getPackageManager()
.getLaunchIntentForPackage(getBaseContext().getPackageName());
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
}
protected int getId() {
return id;
}
protected void writeValuesToFiles(final Map<FieldKey, String> fieldKeyValueMap, final Artwork artwork) {
if (artwork == null) {
writeValuesToFiles(fieldKeyValueMap, null, true);
} else {
writeValuesToFiles(fieldKeyValueMap, artwork, false);
}
}
protected void writeValuesToFiles(final Map<FieldKey, String> fieldKeyValueMap, boolean deleteArtwork) {
writeValuesToFiles(fieldKeyValueMap, null, deleteArtwork);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent) {
super.onActivityResult(requestCode, resultCode, imageReturnedIntent);
switch (requestCode) {
case REQUEST_CODE_SELECT_IMAGE:
if (resultCode == RESULT_OK) {
Uri selectedImage = imageReturnedIntent.getData();
loadImageFromFile(selectedImage);
}
}
}
protected abstract void loadImageFromFile(Uri selectedFile);
protected App getApp() {
return app;
}
protected String getSongTitle() {
try {
return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.TITLE);
} catch (NullPointerException e) {
return null;
}
}
private AudioFile getAudioFile(String path) {
try {
return AudioFileIO.read(new File(path));
} catch (CannotReadException | ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException e) {
Log.e(TAG, "Error while trying to create the AudioFile from java.io.File", e);
}
return null;
}
protected String getAlbumTitle() {
try {
return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.ALBUM);
} catch (NullPointerException ignored) {
}
return null;
}
protected String getArtistName() {
try {
return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.ARTIST);
} catch (NullPointerException ignored) {
}
return null;
}
protected String getAlbumArtistName() {
try {
return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.ALBUM_ARTIST);
} catch (NullPointerException ignored) {
}
return null;
}
protected String getGenreName() {
try {
return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.GENRE);
} catch (NullPointerException ignored) {
}
return null;
}
protected String getSongYear() {
try {
return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.YEAR);
} catch (NullPointerException ignored) {
}
return null;
}
protected String getTrackNumber() {
try {
return getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirst(FieldKey.TRACK);
} catch (NullPointerException ignored) {
}
return null;
}
protected Bitmap getAlbumArt() {
try {
Artwork artworkTag = getAudioFile(songPaths.get(0)).getTagOrCreateAndSetDefault().getFirstArtwork();
if (artworkTag != null) {
byte[] artworkBinaryData = artworkTag.getBinaryData();
return BitmapFactory.decodeByteArray(artworkBinaryData, 0, artworkBinaryData.length);
}
} catch (NullPointerException ignored) {
}
return null;
}
}

View file

@ -0,0 +1,203 @@
package com.kabouzeid.gramophone.ui.activities.tageditor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.lastfm.album.LastFMAlbumImageLoader;
import com.kabouzeid.gramophone.loader.AlbumSongLoader;
import com.kabouzeid.gramophone.loader.SongFilePathLoader;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.Util;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.process.BitmapProcessor;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.images.Artwork;
import org.jaudiotagger.tag.images.ArtworkFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
public class AlbumTagEditorActivity extends AbsTagEditorActivity implements TextWatcher {
public static final String TAG = AlbumTagEditorActivity.class.getSimpleName();
private File albumArtFile;
private Bitmap albumArtBitmap;
private boolean deleteAlbumArt;
private EditText albumTitle;
private EditText albumArtistName;
private EditText genreName;
private EditText year;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initViews();
setUpViews();
}
private void initViews() {
albumTitle = (EditText) findViewById(R.id.album_title);
albumArtistName = (EditText) findViewById(R.id.album_artist);
genreName = (EditText) findViewById(R.id.genre);
year = (EditText) findViewById(R.id.year);
}
private void setUpViews() {
fillViewsWithFileTags();
albumTitle.addTextChangedListener(this);
albumArtistName.addTextChangedListener(this);
genreName.addTextChangedListener(this);
year.addTextChangedListener(this);
}
private void fillViewsWithFileTags() {
albumTitle.setText(getAlbumTitle());
albumArtistName.setText(getAlbumArtistName());
genreName.setText(getGenreName());
year.setText(getSongYear());
}
@Override
protected void loadCurrentImage() {
setImageBitmap(getAlbumArt());
deleteAlbumArt = false;
}
@Override
protected void getImageFromLastFM() {
String albumTitleStr = albumTitle.getText().toString();
String albumArtistNameStr = albumArtistName.getText().toString();
if (albumArtistNameStr.trim().equals("") || albumTitleStr.trim().equals("")) {
Toast.makeText(this, getResources().getString(R.string.album_or_artist_empty), Toast.LENGTH_SHORT).show();
return;
}
LastFMAlbumImageLoader.loadAlbumImage(this, albumTitleStr, albumArtistNameStr, new LastFMAlbumImageLoader.AlbumImageLoaderCallback() {
@Override
public void onAlbumImageLoaded(Bitmap albumImage, String uri) {
if (albumImage != null) {
setImageBitmap(albumImage);
albumArtBitmap = albumImage;
deleteAlbumArt = false;
dataChanged();
Toast.makeText(AlbumTagEditorActivity.this, "Success.", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(AlbumTagEditorActivity.this, "Failed.", Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
protected void searchImageOnWeb() {
List<String> query = new ArrayList<>();
query.add(albumTitle.getText().toString());
query.add(albumArtistName.getText().toString());
searchWebFor(query);
}
@Override
protected void deleteImage() {
setImageRes(R.drawable.default_album_art);
deleteAlbumArt = true;
dataChanged();
}
@Override
protected void save() {
Artwork artwork = null;
Map<FieldKey, String> fieldKeyValueMap = new EnumMap<>(FieldKey.class);
fieldKeyValueMap.put(FieldKey.ALBUM, albumTitle.getText().toString());
//android seems not to recognize album_artist field so we additionally write the normal artist field
fieldKeyValueMap.put(FieldKey.ARTIST, albumArtistName.getText().toString());
fieldKeyValueMap.put(FieldKey.ALBUM_ARTIST, albumArtistName.getText().toString());
fieldKeyValueMap.put(FieldKey.GENRE, genreName.getText().toString());
fieldKeyValueMap.put(FieldKey.YEAR, year.getText().toString());
try {
albumArtFile = MusicUtil.getAlbumArtFile(this, String.valueOf(getId()));
} catch (IOException e) {
Log.e(TAG, "error while creating albumArtFile", e);
}
if (albumArtBitmap != null && albumArtFile != null) {
try {
albumArtBitmap.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile));
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile);
MusicUtil.insertAlbumArt(this, getId(), albumArtFile.getAbsolutePath());
} catch (IOException e) {
Log.e(TAG, "error while trying to create the artwork from file", e);
}
}
writeValuesToFiles(fieldKeyValueMap, artwork, deleteAlbumArt);
}
@Override
protected int getContentViewResId() {
return R.layout.activity_album_tag_editor;
}
@Override
protected List<String> getSongPaths() {
List<Song> songs = AlbumSongLoader.getAlbumSongList(this, getId());
int[] songIds = new int[songs.size()];
for (int i = 0; i < songs.size(); i++) {
songIds[i] = songs.get(i).id;
}
return SongFilePathLoader.getSongFilePaths(this, songIds);
}
@Override
protected void loadImageFromFile(final Uri selectedFileUri) {
DisplayImageOptions options = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(false)
.postProcessor(new BitmapProcessor() {
@Override
public Bitmap process(Bitmap bmp) {
Bitmap scaledBitmap = Util.getAlbumArtScaledBitmap(bmp, true);
bmp.recycle();
return scaledBitmap;
}
})
.build();
albumArtBitmap = ImageLoader.getInstance().loadImageSync(selectedFileUri.toString(), options);
if (albumArtBitmap != null) {
setImageBitmap(albumArtBitmap);
deleteAlbumArt = false;
dataChanged();
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
dataChanged();
}
}

View file

@ -0,0 +1,127 @@
package com.kabouzeid.gramophone.ui.activities.tageditor;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.loader.SongFilePathLoader;
import org.jaudiotagger.tag.FieldKey;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
public class SongTagEditorActivity extends AbsTagEditorActivity implements TextWatcher {
public static final String TAG = SongTagEditorActivity.class.getSimpleName();
private EditText songTitle;
private EditText albumTitle;
private EditText artistName;
private EditText genreName;
private EditText year;
private EditText trackNumber;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setNoImageMode();
initViews();
setUpViews();
}
private void initViews() {
songTitle = (EditText) findViewById(R.id.title1);
albumTitle = (EditText) findViewById(R.id.title2);
artistName = (EditText) findViewById(R.id.artist);
genreName = (EditText) findViewById(R.id.genre);
year = (EditText) findViewById(R.id.year);
trackNumber = (EditText) findViewById(R.id.track_number);
}
private void setUpViews() {
fillViewsWithFileTags();
songTitle.addTextChangedListener(this);
albumTitle.addTextChangedListener(this);
artistName.addTextChangedListener(this);
genreName.addTextChangedListener(this);
year.addTextChangedListener(this);
trackNumber.addTextChangedListener(this);
}
private void fillViewsWithFileTags() {
songTitle.setText(getSongTitle());
albumTitle.setText(getAlbumTitle());
artistName.setText(getArtistName());
genreName.setText(getGenreName());
year.setText(getSongYear());
trackNumber.setText(getTrackNumber());
}
@Override
protected void loadCurrentImage() {
}
@Override
protected void getImageFromLastFM() {
}
@Override
protected void searchImageOnWeb() {
}
@Override
protected void deleteImage() {
}
@Override
protected void save() {
Map<FieldKey, String> fieldKeyValueMap = new EnumMap<>(FieldKey.class);
fieldKeyValueMap.put(FieldKey.TITLE, songTitle.getText().toString());
fieldKeyValueMap.put(FieldKey.ALBUM, albumTitle.getText().toString());
fieldKeyValueMap.put(FieldKey.ARTIST, artistName.getText().toString());
fieldKeyValueMap.put(FieldKey.GENRE, genreName.getText().toString());
fieldKeyValueMap.put(FieldKey.YEAR, year.getText().toString());
fieldKeyValueMap.put(FieldKey.TRACK, trackNumber.getText().toString());
writeValuesToFiles(fieldKeyValueMap);
}
@Override
protected int getContentViewResId() {
return R.layout.activity_song_tag_editor;
}
@Override
protected List<String> getSongPaths() {
return SongFilePathLoader.getSongFilePaths(this, new int[]{getId()});
}
@Override
protected void loadImageFromFile(Uri imageFilePath) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
dataChanged();
}
}

View file

@ -0,0 +1,197 @@
package com.kabouzeid.gramophone.ui.fragments;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.NavigationDrawerItemAdapter;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.NavigationDrawerItem;
import com.nhaarman.listviewanimations.appearance.simple.AlphaInAnimationAdapter;
import java.util.ArrayList;
public class NavigationDrawerFragment extends Fragment {
public static final int NAVIGATION_DRAWER_HEADER = -1;
private static final String TAG = NavigationDrawerFragment.class.getSimpleName();
private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position";
public View fragmentRootView;
private App app;
private NavigationDrawerCallbacks callbacks;
private NavigationDrawerItemAdapter drawerAdapter;
private DrawerLayout drawerLayout;
private ListView drawerListView;
private View fragmentContainerView;
private Button headerButton;
private ImageView albumArt;
private TextView songTitle;
private TextView songArtist;
private boolean fromSavedInstanceState;
private boolean userLearnedDrawer;
private int checkedPosition = 0;
public NavigationDrawerFragment() {
}
public boolean isDrawerOpen() {
return drawerLayout != null && drawerLayout.isDrawerOpen(fragmentContainerView);
}
public void setUp(int fragmentId, final DrawerLayout drawerLayout) {
fragmentContainerView = getActivity().findViewById(fragmentId);
this.drawerLayout = drawerLayout;
this.drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
if (!userLearnedDrawer && !fromSavedInstanceState) {
this.drawerLayout.openDrawer(fragmentContainerView);
userLearnedDrawer = true;
app.getDefaultSharedPreferences().edit().putBoolean(AppKeys.SP_USER_LEARNED_DRAWER, true).apply();
}
}
public TextView getSongArtist() {
return songArtist;
}
public ImageView getAlbumArtImageView() {
return albumArt;
}
public TextView getSongTitle() {
return songTitle;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
callbacks = (NavigationDrawerCallbacks) activity;
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
app = (App) getActivity().getApplicationContext();
userLearnedDrawer = app.getDefaultSharedPreferences().getBoolean(AppKeys.SP_USER_LEARNED_DRAWER, false);
if (savedInstanceState != null) {
setItemChecked(savedInstanceState.getInt(STATE_SELECTED_POSITION));
fromSavedInstanceState = true;
}
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_navigation_drawer, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
fragmentRootView = view;
super.onViewCreated(view, savedInstanceState);
initViews();
setUpViews();
}
private void initViews() {
drawerListView = (ListView) fragmentRootView.findViewById(R.id.navigation_drawer_list);
final View drawerHeader = fragmentRootView.findViewById(R.id.header);
headerButton = (Button) drawerHeader.findViewById(R.id.header_clickable);
albumArt = (ImageView) drawerHeader.findViewById(R.id.album_art);
songTitle = (TextView) drawerHeader.findViewById(R.id.song_title);
songArtist = (TextView) drawerHeader.findViewById(R.id.song_artist);
}
private void setUpViews() {
headerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
selectItem(NAVIGATION_DRAWER_HEADER);
}
});
setUpListView();
}
private void setUpListView() {
final ArrayList<NavigationDrawerItem> navigationDrawerItems = new ArrayList<>();
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.songs), R.drawable.ic_my_library_music_white_24dp));
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.albums), R.drawable.ic_album_white_24dp));
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.artists), R.drawable.ic_person_white_24dp));
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.genres), R.drawable.ic_my_library_music_white_24dp));
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.playlists), R.drawable.ic_queue_music_white_24dp));
drawerAdapter = new NavigationDrawerItemAdapter(getActivity(), R.id.navigation_drawer, navigationDrawerItems);
final AlphaInAnimationAdapter animationAdapter = new AlphaInAnimationAdapter(drawerAdapter);
animationAdapter.setAbsListView(drawerListView);
drawerListView.setAdapter(animationAdapter);
drawerListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
});
}
private void selectItem(final int position) {
if (position != NAVIGATION_DRAWER_HEADER) {
setItemChecked(position);
if (drawerLayout != null) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
drawerLayout.closeDrawer(fragmentContainerView);
}
}, 400);
}
}
if (callbacks != null) {
callbacks.onNavigationDrawerItemSelected(position);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_SELECTED_POSITION, checkedPosition);
}
@Override
public void onDetach() {
super.onDetach();
callbacks = null;
}
public void setItemChecked(final int position) {
if (drawerAdapter != null) {
drawerAdapter.setChecked(position);
checkedPosition = position;
}
}
public static interface NavigationDrawerCallbacks {
void onNavigationDrawerItemSelected(int position);
}
}

View file

@ -0,0 +1,151 @@
package com.kabouzeid.gramophone.ui.fragments.artistviewpager;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import com.github.ksoichiro.android.observablescrollview.ObservableGridView;
import com.github.ksoichiro.android.observablescrollview.ObservableScrollViewCallbacks;
import com.github.ksoichiro.android.observablescrollview.ScrollState;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.interfaces.KabViewsDisableAble;
import com.kabouzeid.gramophone.ui.activities.ArtistDetailActivity;
import com.kabouzeid.gramophone.util.Util;
public abstract class AbsViewPagerTabArtistListFragment extends Fragment implements ObservableScrollViewCallbacks, KabViewsDisableAble {
public static final String TAG = AbsViewPagerTabArtistListFragment.class.getSimpleName();
protected App app;
private ObservableGridView observableGridView;
private Activity parentActivity;
private int artistId = -1;
private String artistName = "";
private int paddingViewHeight;
private boolean areViewsEnabled;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
app = (App) getActivity().getApplicationContext();
parentActivity = getActivity();
getArgs();
View view = inflater.inflate(R.layout.fragment_gridview, container, false);
observableGridView = (ObservableGridView) view.findViewById(R.id.scroll);
setGridViewPadding();
observableGridView.setScrollViewCallbacks(this);
ListAdapter adapter = getAdapter();
if (adapter != null) {
observableGridView.setAdapter(adapter);
}
return view;
}
private void setGridViewPadding() {
final int artistImageViewHeight = getResources().getDimensionPixelSize(R.dimen.header_image_height);
final int titleViewHeight = getResources().getDimensionPixelSize(R.dimen.title_view_height);
final int tabHeight = getResources().getDimensionPixelSize(R.dimen.tab_height);
paddingViewHeight = artistImageViewHeight + titleViewHeight + tabHeight;
if (app.isInPortraitMode() || app.isTablet()) {
observableGridView.setPadding(0, paddingViewHeight, 0, Util.getNavigationBarHeight(getActivity()));
} else {
observableGridView.setPadding(0, paddingViewHeight, 0, 0);
}
}
private void getArgs() {
Bundle args = getArguments();
if (args != null) {
artistId = args.getInt(ArtistDetailActivity.ARG_ARTIST_ID, -1);
artistName = args.getString(ArtistDetailActivity.ARG_ARTIST_NAME, "");
}
}
/*
*
* IMPORTANT:
*
* You CAN return null here and use setAdapter(ListAdapter adapter) inside getAdapter() to manually set the adapter.
*
* (i.e. if you must set the adapter async).
*
* */
protected abstract ListAdapter getAdapter();
protected void setAdapter(ListAdapter adapter) {
observableGridView.setAdapter(adapter);
}
@Override
public void onResume() {
super.onResume();
enableViews();
}
@Override
public void enableViews() {
areViewsEnabled = true;
observableGridView.setEnabled(true);
}
@Override
public void disableViews() {
areViewsEnabled = false;
observableGridView.setEnabled(false);
}
@Override
public boolean areViewsEnabled() {
return areViewsEnabled;
}
public int getY() {
return observableGridView.getCurrentScrollY() + paddingViewHeight;
}
protected int getArtistId() {
return artistId;
}
protected String getArtistName() {
return artistName;
}
public Activity getParentActivity() {
return parentActivity;
}
protected void setOnItemClickListener(AdapterView.OnItemClickListener onItemClickListener) {
observableGridView.setOnItemClickListener(onItemClickListener);
}
protected void setColumns(int columns) {
observableGridView.setNumColumns(columns);
}
@Override
public void onScrollChanged(int scrollY, boolean b, boolean b2) {
if (parentActivity instanceof ObservableScrollViewCallbacks) {
if (getUserVisibleHint()) {
((ObservableScrollViewCallbacks) parentActivity).onScrollChanged(scrollY + paddingViewHeight, b, b2);
}
}
}
@Override
public void onDownMotionEvent() {
}
@Override
public void onUpOrCancelMotionEvent(ScrollState scrollState) {
}
}

View file

@ -0,0 +1,86 @@
package com.kabouzeid.gramophone.ui.fragments.artistviewpager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.util.Pair;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.AlbumViewGridAdapter;
import com.kabouzeid.gramophone.comparator.AlbumAlphabeticComparator;
import com.kabouzeid.gramophone.interfaces.KabViewsDisableAble;
import com.kabouzeid.gramophone.loader.ArtistAlbumLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.Album;
import com.kabouzeid.gramophone.ui.activities.AlbumDetailActivity;
import com.melnykov.fab.FloatingActionButton;
import java.util.Collections;
import java.util.List;
/**
* Created by karim on 04.01.15.
*/
public class ViewPagerTabArtistAlbumFragment extends AbsViewPagerTabArtistListFragment {
private FloatingActionButton fab;
@Override
protected ListAdapter getAdapter() {
List<Album> albums = ArtistAlbumLoader.getArtistAlbumList(getParentActivity(), getArtistId());
Collections.sort(albums, new AlbumAlphabeticComparator());
ListAdapter adapter = new AlbumViewGridAdapter(getParentActivity(), albums);
setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Album album = (Album) parent.getItemAtPosition(position);
View albumArtView = view.findViewById(R.id.album_art);
openAlbumDetailsActivityIfPossible(album, albumArtView);
}
});
setColumns(getResources().getInteger(R.integer.grid_columns));
return adapter;
}
@SuppressWarnings("unchecked")
private void openAlbumDetailsActivityIfPossible(Album album, View albumArtForTransition) {
if (areParentActivitiesViewsEnabled()) {
disableViews();
disableParentActivitiesViews();
final Intent intent = new Intent(getActivity(), AlbumDetailActivity.class);
intent.putExtra(AppKeys.E_ALBUM, album.id);
final ActivityOptionsCompat activityOptions;
if (fab != null && albumArtForTransition != null) {
activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
Pair.create(albumArtForTransition, getString(R.string.transition_album_cover)),
Pair.create((View) fab, getString(R.string.transition_fab))
);
} else {
activityOptions = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity());
}
ActivityCompat.startActivity(getActivity(), intent, activityOptions.toBundle());
}
}
private void disableParentActivitiesViews() {
if (getParentActivity() instanceof KabViewsDisableAble) {
((KabViewsDisableAble) getParentActivity()).disableViews();
}
}
private boolean areParentActivitiesViewsEnabled() {
return !(getParentActivity() instanceof KabViewsDisableAble) || ((KabViewsDisableAble) getParentActivity()).areViewsEnabled();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
fab = (FloatingActionButton) getParentActivity().findViewById(R.id.fab);
}
}

View file

@ -0,0 +1,71 @@
package com.kabouzeid.gramophone.ui.fragments.artistviewpager;
import android.content.Context;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.TextView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.lastfm.artist.LastFMArtistBiographyLoader;
import java.util.ArrayList;
import java.util.List;
public class ViewPagerTabArtistBioFragment extends AbsViewPagerTabArtistListFragment {
@Override
protected ListAdapter getAdapter() {
final List<String> strings = new ArrayList<>();
strings.add("loading");
ListAdapter adapter = new SimpleTextAdapter(getParentActivity(), strings);
setAdapter(adapter);
LastFMArtistBiographyLoader.loadArtistBio(getParentActivity(), getArtistName(), new LastFMArtistBiographyLoader.ArtistBioLoaderCallback() {
@Override
public void onArtistBioLoaded(String biography) {
if (biography == null || biography.trim().equals("")) {
try {
biography = getResources().getString(R.string.biography_unavailable);
} catch (IllegalStateException e) {
Log.e(TAG, "error while trying to access resources", e);
biography = "Error";
}
}
strings.clear();
strings.add(biography);
ListAdapter adapter = new SimpleTextAdapter(getParentActivity(), strings);
setAdapter(adapter);
}
});
return null;
}
private static class SimpleTextAdapter extends ArrayAdapter<String> {
private Context context;
public SimpleTextAdapter(Context context, List<String> objects) {
super(context, R.layout.item_artist_details_biography, objects);
this.context = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String string = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(context).inflate(R.layout.item_artist_details_biography, parent, false);
}
TextView text = (TextView) convertView.findViewById(R.id.text);
text.setText(Html.fromHtml(string));
text.setMovementMethod(LinkMovementMethod.getInstance());
return convertView;
}
}
}

View file

@ -0,0 +1,37 @@
package com.kabouzeid.gramophone.ui.fragments.artistviewpager;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import com.kabouzeid.gramophone.adapter.songadapter.SongAdapter;
import com.kabouzeid.gramophone.comparator.SongAlphabeticComparator;
import com.kabouzeid.gramophone.loader.ArtistSongLoader;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.base.AbsBaseActivity;
import java.util.Collections;
import java.util.List;
/**
* Created by karim on 04.01.15.
*/
public class ViewPagerTabArtistSongListFragment extends AbsViewPagerTabArtistListFragment {
@Override
protected ListAdapter getAdapter() {
final List<Song> songs = ArtistSongLoader.getArtistSongList(getParentActivity(), getArtistId());
Collections.sort(songs, new SongAlphabeticComparator());
AbsBaseActivity absBaseActivity = null;
if (getParentActivity() instanceof AbsBaseActivity) {
absBaseActivity = (AbsBaseActivity) getParentActivity();
}
ListAdapter adapter = new SongAdapter(getParentActivity(), absBaseActivity, songs);
setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
app.getMusicPlayerRemote().openQueue(songs, position, true);
}
});
return adapter;
}
}

View file

@ -0,0 +1,114 @@
package com.kabouzeid.gramophone.ui.fragments.mainactivityfragments;
import android.os.Bundle;
import android.support.v4.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.AlbumViewGridAdapter;
import com.kabouzeid.gramophone.comparator.AlbumAlphabeticComparator;
import com.kabouzeid.gramophone.loader.AlbumLoader;
import com.kabouzeid.gramophone.model.Album;
import java.util.Collections;
import java.util.List;
/**
* Created by karim on 22.11.14.
*/
public class AlbumViewFragment extends MainActivityFragment {
public static final String TAG = AlbumViewFragment.class.getSimpleName();
private App app;
private AbsListView absListView;
private View fragmentRootView;
@Override
public void onCreate(Bundle savedInstanceState) {
app = (App) getActivity().getApplicationContext();
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_albumview, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
fragmentRootView = view;
super.onViewCreated(view, savedInstanceState);
initViews();
setUpViews();
}
@Override
public void enableViews() {
super.enableViews();
absListView.setEnabled(true);
}
@Override
public void disableViews() {
super.disableViews();
absListView.setEnabled(false);
}
private void initViews() {
absListView = (AbsListView) fragmentRootView.findViewById(R.id.absList);
}
private void setUpViews() {
setUpAbsListView();
}
private void setUpAbsListView() {
List<Album> albums = AlbumLoader.getAllAlbums(getActivity());
fillAbsListView(albums);
}
private void fillAbsListView(List<Album> albums) {
Collections.sort(albums, new AlbumAlphabeticComparator());
AlbumViewGridAdapter albumViewGridAdapter = new AlbumViewGridAdapter(getActivity(), albums);
absListView.setAdapter(albumViewGridAdapter);
absListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Album album = (Album) parent.getItemAtPosition(position);
View albumArtView = view.findViewById(R.id.album_art);
openAlbumDetailsActivity(album, albumArtView);
}
});
absListView.setPadding(0, getTopPadding(app), 0, getBottomPadding(app));
}
private void openAlbumDetailsActivity(Album album, View albumArtForTransition) {
getMainActivity().goToAlbum(album.id, new Pair[]{
Pair.create(albumArtForTransition, getString(R.string.transition_album_cover))
});
}
@Override
public void search(String query) {
setUpAbsListView(query);
}
private void setUpAbsListView(String query) {
List<Album> albums = AlbumLoader.getAlbums(getActivity(), query);
fillAbsListView(albums);
}
@Override
public void returnToNonSearch() {
setUpAbsListView();
}
}

View file

@ -0,0 +1,124 @@
package com.kabouzeid.gramophone.ui.fragments.mainactivityfragments;
import android.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.ArtistViewListAdapter;
import com.kabouzeid.gramophone.comparator.ArtistAlphabeticComparator;
import com.kabouzeid.gramophone.loader.ArtistLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.Artist;
import com.kabouzeid.gramophone.ui.activities.ArtistDetailActivity;
import com.kabouzeid.gramophone.ui.activities.base.AbsFabActivity;
import java.util.Collections;
import java.util.List;
/**
* A simple {@link Fragment} subclass.
*/
public class ArtistViewFragment extends MainActivityFragment {
public static final String TAG = ArtistViewFragment.class.getSimpleName();
private App app;
private AbsListView absListView;
private View fragmentRootView;
private boolean areViewsEnabled;
@Override
public void onCreate(Bundle savedInstanceState) {
app = (App) getActivity().getApplicationContext();
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_artist_view, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
fragmentRootView = view;
super.onViewCreated(view, savedInstanceState);
initViews();
setUpViews();
}
@Override
public void enableViews() {
super.enableViews();
absListView.setEnabled(true);
}
@Override
public void disableViews() {
super.disableViews();
absListView.setEnabled(false);
}
private void initViews() {
absListView = (AbsListView) fragmentRootView.findViewById(R.id.absList);
}
private void setUpViews() {
setUpAbsListView();
}
private void setUpAbsListView() {
List<Artist> artists = ArtistLoader.getAllArtists(getActivity());
fillAbsListView(artists);
}
private void fillAbsListView(List<Artist> artists) {
Collections.sort(artists, new ArtistAlphabeticComparator());
ArtistViewListAdapter artistAdapter = new ArtistViewListAdapter(getActivity(), artists);
absListView.setAdapter(artistAdapter);
absListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Artist artist = (Artist) parent.getItemAtPosition(position);
final View artistImageView = view.findViewById(R.id.artist_image);
if (getActivity() instanceof AbsFabActivity) {
AbsFabActivity absFabActivity = (AbsFabActivity) getActivity();
Pair[] sharedElements = {Pair.create(artistImageView, getString(R.string.transition_artist_image))};
absFabActivity.goToArtist(artist.id, sharedElements);
} else {
Intent intent = new Intent(getActivity(), ArtistDetailActivity.class);
intent.putExtra(AppKeys.E_ARTIST, artist.id);
startActivity(intent);
}
}
});
absListView.setPadding(0, getTopPadding(app), 0, getBottomPadding(app));
}
@Override
public void search(String query) {
setUpAbsListView(query);
}
private void setUpAbsListView(String query) {
List<Artist> artists = ArtistLoader.getArtists(getActivity(), query);
fillAbsListView(artists);
}
@Override
public void returnToNonSearch() {
setUpAbsListView();
}
}

View file

@ -0,0 +1,62 @@
package com.kabouzeid.gramophone.ui.fragments.mainactivityfragments;
import android.app.Fragment;
import android.os.Build;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.interfaces.KabSearchAbleFragment;
import com.kabouzeid.gramophone.interfaces.KabViewsDisableAble;
import com.kabouzeid.gramophone.ui.activities.MainActivity;
import com.kabouzeid.gramophone.util.Util;
/**
* Created by karim on 27.02.15.
*/
public abstract class MainActivityFragment extends Fragment implements KabViewsDisableAble, KabSearchAbleFragment {
private boolean areViewsEnabled;
protected int getTopPadding(App app) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (app.isInPortraitMode() || app.isTablet()) {
return Util.getActionBarSize(getActivity()) + getResources().getDimensionPixelSize(R.dimen.tab_height) + Util.getStatusBarHeight(getActivity());
}
return Util.getActionBarSize(getActivity()) + getResources().getDimensionPixelSize(R.dimen.tab_height) + Util.getStatusBarHeight(getActivity());
}
return Util.getActionBarSize(getActivity()) + getResources().getDimensionPixelSize(R.dimen.tab_height);
}
protected int getBottomPadding(App app) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (app.isInPortraitMode() || app.isTablet()) {
return Util.getNavigationBarHeight(getActivity());
}
}
return 0;
}
@Override
public void enableViews() {
areViewsEnabled = true;
}
@Override
public void disableViews() {
areViewsEnabled = false;
}
@Override
public boolean areViewsEnabled() {
return areViewsEnabled;
}
@Override
public void onResume() {
super.onResume();
enableViews();
}
protected MainActivity getMainActivity() {
return (MainActivity) getActivity();
}
}

View file

@ -0,0 +1,109 @@
package com.kabouzeid.gramophone.ui.fragments.mainactivityfragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.songadapter.SongViewListAdapter;
import com.kabouzeid.gramophone.comparator.SongAlphabeticComparator;
import com.kabouzeid.gramophone.loader.SongLoader;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.base.AbsBaseActivity;
import java.util.Collections;
import java.util.List;
/**
* Created by karim on 29.12.14.
*/
public class SongViewFragment extends MainActivityFragment {
public static final String TAG = SongViewFragment.class.getSimpleName();
private App app;
private AbsListView absListView;
private View fragmentRootView;
@Override
public void onCreate(Bundle savedInstanceState) {
app = (App) getActivity().getApplicationContext();
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_songview, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
fragmentRootView = view;
super.onViewCreated(view, savedInstanceState);
initViews();
setUpViews();
}
private void initViews() {
absListView = (AbsListView) fragmentRootView.findViewById(R.id.absList);
}
private void setUpViews() {
setUpAbsListView();
}
private void setUpAbsListView() {
List<Song> songs = SongLoader.getAllSongs(getActivity());
fillAbsListView(songs);
}
private void fillAbsListView(final List<Song> songs) {
Collections.sort(songs, new SongAlphabeticComparator());
AbsBaseActivity absBaseActivity = null;
if (getActivity() instanceof AbsBaseActivity) {
absBaseActivity = (AbsBaseActivity) getActivity();
}
SongViewListAdapter songAdapter = new SongViewListAdapter(getActivity(), absBaseActivity, songs);
absListView.setAdapter(songAdapter);
absListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
app.getMusicPlayerRemote().openQueue(songs, position, true);
}
});
absListView.setPadding(0, getTopPadding(app), 0, getBottomPadding(app));
}
@Override
public void search(String query) {
setUpAbsListView(query);
}
private void setUpAbsListView(String query) {
List<Song> songs = SongLoader.getSongs(getActivity(), query);
fillAbsListView(songs);
}
@Override
public void returnToNonSearch() {
setUpAbsListView();
}
@Override
public void enableViews() {
super.enableViews();
absListView.setEnabled(true);
}
@Override
public void disableViews() {
super.disableViews();
absListView.setEnabled(false);
}
}

View file

@ -0,0 +1,92 @@
package com.kabouzeid.gramophone.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.view.View;
import android.widget.ImageView;
import com.kabouzeid.gramophone.R;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import com.nostra13.universalimageloader.utils.L;
/**
* Created by karim on 28.12.14.
*/
public class ImageLoaderUtil {
public static void initImageLoader(Context context) {
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(false)
.build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.defaultDisplayImageOptions(defaultOptions)
//.memoryCache(new LRULimitedMemoryCache(1024*1024*CACHE_SIZE_MB))
.build();
ImageLoader.getInstance().init(config);
L.writeLogs(false);
}
public static DisplayImageOptions getCacheOnDiskOptions() {
return new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(true)
.build();
}
public static DisplayImageOptions getCacheInMemoryOptions() {
return new DisplayImageOptions.Builder()
.cacheInMemory(true)
.cacheOnDisk(false)
.build();
}
public static class defaultAlbumArtOnFailed implements ImageLoadingListener {
@Override
public void onLoadingStarted(String imageUri, View view) {
if (view != null) ((ImageView) view).setImageResource(R.drawable.default_album_art);
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
if (view != null) ((ImageView) view).setImageResource(R.drawable.default_album_art);
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
if (view != null) ((ImageView) view).setImageResource(R.drawable.default_album_art);
}
}
public static class defaultArtistArtOnFailed implements ImageLoadingListener {
@Override
public void onLoadingStarted(String imageUri, View view) {
if (view != null) ((ImageView) view).setImageResource(R.drawable.default_artist_image);
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
if (view != null) ((ImageView) view).setImageResource(R.drawable.default_artist_image);
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
}
@Override
public void onLoadingCancelled(String imageUri, View view) {
if (view != null) ((ImageView) view).setImageResource(R.drawable.default_artist_image);
}
}
}

View file

@ -0,0 +1,50 @@
package com.kabouzeid.gramophone.util;
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* Created by karim on 22.12.14.
*/
public final class InternalStorageUtil {
private static final String TAG = InternalStorageUtil.class.getSimpleName();
public static synchronized void writeObject(final Context context, final String key, final Object object) throws IOException {
try {
String tempFileName = "TEMP_" + key;
FileOutputStream fos;
fos = context.openFileOutput(tempFileName, Context.MODE_PRIVATE);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(object);
oos.close();
fos.close();
renameAppFile(context, tempFileName, key);
} catch (IOException e) {
Log.e(TAG, "Writing Object to internal storage failed! Maybe the Object is not serializable?", e);
}
}
public static synchronized void renameAppFile(final Context context, String originalFileName, String newFileName) {
File originalFile = context.getFileStreamPath(originalFileName);
File newFile = new File(originalFile.getParent(), newFileName);
if (newFile.exists()) {
context.deleteFile(newFileName);
}
//noinspection ResultOfMethodCallIgnored
originalFile.renameTo(newFile);
}
public static synchronized Object readObject(final Context context, String key) throws IOException,
ClassNotFoundException {
FileInputStream fis = context.openFileInput(key);
ObjectInputStream ois = new ObjectInputStream(fis);
return ois.readObject();
}
}

View file

@ -0,0 +1,76 @@
package com.kabouzeid.gramophone.util;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.IOException;
/**
* Created by karim on 29.12.14.
*/
public class MusicUtil {
public static final String TAG = MusicUtil.class.getSimpleName();
public static Uri getAlbumArtUri(int album_id) {
final Uri sArtworkUri = Uri
.parse("content://media/external/audio/albumart");
return ContentUris.withAppendedId(sArtworkUri, album_id);
}
public static String getReadableDurationString(long songDurationMillis) {
long minutes = (songDurationMillis / 1000) / 60;
long seconds = (songDurationMillis / 1000) % 60;
return String.format("%02d:%02d", minutes, seconds);
}
//iTunes uses for example 1002 for track 2 CD1 or 3011 for track 11 CD3.
//this method converts those values to normal tracknumbers
public static int getFixedTrackNumber(int trackNumberToFix) {
return trackNumberToFix % 1000;
}
public static void insertAlbumArt(Context context, int albumId, String path) {
ContentResolver contentResolver = context.getContentResolver();
Uri artworkUri = Uri.parse("content://media/external/audio/albumart");
contentResolver.delete(ContentUris.withAppendedId(artworkUri, albumId), null, null);
ContentValues values = new ContentValues();
values.put("album_id", albumId);
values.put("_data", path);
contentResolver.insert(artworkUri, values);
}
public static void deleteAlbumArt(Context context, int albumId) {
ContentResolver contentResolver = context.getContentResolver();
Uri localUri = Uri.parse("content://media/external/audio/albumart");
contentResolver.delete(ContentUris.withAppendedId(localUri, albumId), null, null);
}
public static File getAlbumArtFile(Context context, String name)
throws IOException {
return new File(createAlbumArtDir(context), name + System.currentTimeMillis());
}
public static File createAlbumArtDir(Context paramContext) {
File albumArtDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "/.albumart/");
if (!albumArtDir.exists()) {
albumArtDir.mkdirs();
try {
new File(albumArtDir, ".nomedia").createNewFile();
} catch (IOException e) {
Log.e(TAG, "error while creating .nomedia file", e);
}
}
return albumArtDir;
}
}

View file

@ -0,0 +1,203 @@
package com.kabouzeid.gramophone.util;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.util.TypedValue;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.misc.AppKeys;
/**
* Created by karim on 12.12.14.
*/
public class Util {
private static int albumArtSize = 600;
public static int resolveDrawable(Context context, int drawable) {
TypedArray a = context.obtainStyledAttributes(new int[]{drawable});
int resId = a.getResourceId(0, 0);
a.recycle();
return resId;
}
public static int resolveColor(Context context, int color) {
TypedArray a = context.obtainStyledAttributes(new int[]{color});
int resId = a.getColor(0, context.getResources().getColor(R.color.materialmusic_color));
a.recycle();
return resId;
}
public static boolean isWindowTranslucent(Context context) {
TypedArray a = context.obtainStyledAttributes(new int[]{android.R.attr.windowTranslucentStatus});
boolean result = a.getBoolean(0, false);
a.recycle();
return result;
}
public static int getActionBarSize(Context context) {
TypedValue typedValue = new TypedValue();
int[] textSizeAttr = new int[]{R.attr.actionBarSize};
int indexOfAttrTextSize = 0;
TypedArray a = context.obtainStyledAttributes(typedValue.data, textSizeAttr);
int actionBarSize = a.getDimensionPixelSize(indexOfAttrTextSize, -1);
a.recycle();
return actionBarSize;
}
public static int getStatusBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
public static int getNavigationBarHeight(Context context) {
int result = 0;
int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
result = context.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
@TargetApi(19)
public static void setNavBarTranslucent(Window window, boolean translucent) {
if (translucent) {
window.setFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
return;
}
final WindowManager.LayoutParams attrs = window
.getAttributes();
attrs.flags &= (~WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
window.setAttributes(attrs);
window.clearFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
@TargetApi(19)
public static void setStatusBarTranslucent(Window window, boolean translucent) {
if (translucent) {
window.setFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
return;
}
final WindowManager.LayoutParams attrs = window
.getAttributes();
attrs.flags &= (~WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.setAttributes(attrs);
window.clearFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
}
public static final boolean isOnline(final Context context) {
if (context == null) {
return false;
}
boolean state = false;
final boolean onlyOnWifi = ((App) context.getApplicationContext()).getDefaultSharedPreferences().getBoolean(AppKeys.SP_ONLY_ON_WIFI, true);
/* Monitor network connections */
final ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
/* Wi-Fi connection */
final NetworkInfo wifiNetwork = connectivityManager
.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (wifiNetwork != null) {
state = wifiNetwork.isConnectedOrConnecting();
}
/* Mobile data connection */
final NetworkInfo mbobileNetwork = connectivityManager
.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
if (mbobileNetwork != null) {
if (!onlyOnWifi) {
state = mbobileNetwork.isConnectedOrConnecting();
}
}
/* Other networks */
final NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
if (activeNetwork != null) {
if (!onlyOnWifi) {
state = activeNetwork.isConnectedOrConnecting();
}
}
return state;
}
public static String getFileSizeString(long sizeInBytes) {
long fileSizeInKB = sizeInBytes / 1024;
long fileSizeInMB = fileSizeInKB / 1024;
return fileSizeInMB + " MB";
}
public static String getFilePathFromContentProviderUri(Context context, Uri uri) {
String path = "";
String[] projection = {MediaStore.MediaColumns.DATA};
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
if (cursor == null) return null;
int column_index = cursor.getColumnIndexOrThrow(projection[0]);
if (cursor.moveToFirst()) {
path = cursor.getString(column_index);
}
cursor.close();
return path;
}
public static Bitmap getAlbumArtScaledBitmap(final Bitmap bitmap, boolean keepAspectRatio) {
if (keepAspectRatio) {
double aspectRatio = (double) bitmap.getHeight() / (double) bitmap.getWidth();
int targetWidth = albumArtSize;
int targetHeight = (int) (targetWidth * aspectRatio);
return Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, false);
} else {
return getScaledBitmap(bitmap);
}
}
private static Bitmap getScaledBitmap(final Bitmap bitmap) {
return Bitmap.createScaledBitmap(bitmap, albumArtSize, albumArtSize, false);
}
public static void hideSoftKeyboard(Activity activity) {
if (activity != null) {
View currentFocus = activity.getCurrentFocus();
if (currentFocus != null) {
InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
}
}
}
public static boolean hasLollipopSDK() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
public static boolean hasKitKatSDK() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
}

View file

@ -0,0 +1,98 @@
package com.kabouzeid.gramophone.util;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.PathInterpolator;
import android.widget.ListAdapter;
import android.widget.ListView;
/**
* Created by karim on 06.12.14.
*/
public class ViewUtil {
public final static int DEFAULT_COLOR_ANIMATION_DURATION = 1000;
public static void disableViews(ViewGroup layout) {
for (int i = 0; i < layout.getChildCount(); i++) {
View child = layout.getChildAt(i);
if (child instanceof ViewGroup) {
disableViews((ViewGroup) child);
} else {
child.setEnabled(false);
}
}
}
public static void enableViews(ViewGroup layout) {
for (int i = 0; i < layout.getChildCount(); i++) {
View child = layout.getChildAt(i);
if (child instanceof ViewGroup) {
enableViews((ViewGroup) child);
} else {
child.setEnabled(true);
}
}
}
public static void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null)
return;
int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.UNSPECIFIED);
int totalHeight = 0;
View view = null;
for (int i = 0; i < listAdapter.getCount(); i++) {
view = listAdapter.getView(i, view, listView);
if (i == 0)
view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT));
view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
public static void animateViewColor(final View v, final int startColor, final int endColor) {
animateViewColor(v, startColor, endColor, DEFAULT_COLOR_ANIMATION_DURATION);
}
public static void animateViewColor(final View v, final int startColor, final int endColor, final int duration) {
ObjectAnimator animator = ObjectAnimator.ofObject(v, "backgroundColor",
new ArgbEvaluator(), startColor, endColor);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animator.setInterpolator(new PathInterpolator(0.4f, 0f, 1f, 1f));
}
animator.setDuration(duration);
animator.start();
}
public static void setBackgroundAlpha(View view, float alpha, int baseColor) {
int a = Math.min(255, Math.max(0, (int) (alpha * 255))) << 24;
int rgb = 0x00ffffff & baseColor;
view.setBackgroundColor(a + rgb);
}
public static void addOnGlobalLayoutListener(final View view, final Runnable runnable) {
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
runnable.run();
}
});
}
}

View file

@ -0,0 +1,29 @@
package com.kabouzeid.gramophone.view;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* Created by karim on 22.11.14.
*/
public class SquareImageView extends ImageView {
public SquareImageView(Context context) {
super(context);
}
public SquareImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
}