From 9755e877f98d54c2e33ccfdb2a51b3a4cd7e2035 Mon Sep 17 00:00:00 2001 From: Karim Abou Zeid Date: Sat, 30 Sep 2017 12:58:07 +0200 Subject: [PATCH] Add custom artist images --- .../java/com/kabouzeid/gramophone/App.java | 4 + .../gramophone/adapter/SearchAdapter.java | 17 +-- .../adapter/artist/ArtistAdapter.java | 20 +-- .../gramophone/glide/ArtistGlideRequest.java | 134 ++++++++++++++++++ .../ui/activities/ArtistDetailActivity.java | 74 ++++------ .../tageditor/AbsTagEditorActivity.java | 3 +- .../tageditor/AlbumTagEditorActivity.java | 31 +--- .../util/CustomArtistImageUtil.java | 132 +++++++++++++++++ .../kabouzeid/gramophone/util/ImageUtil.java | 29 ++++ app/src/main/res/menu/menu_artist_detail.xml | 9 +- app/src/main/res/values/strings.xml | 2 + 11 files changed, 350 insertions(+), 105 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/glide/ArtistGlideRequest.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/util/CustomArtistImageUtil.java diff --git a/app/src/main/java/com/kabouzeid/gramophone/App.java b/app/src/main/java/com/kabouzeid/gramophone/App.java index 6e241d17..bcdd52c3 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/App.java +++ b/app/src/main/java/com/kabouzeid/gramophone/App.java @@ -65,6 +65,10 @@ public class App extends Application { return BuildConfig.DEBUG || app.billingProcessor.isPurchased(PRO_VERSION_PRODUCT_ID); } + public static App getInstance() { + return app; + } + @Override public void onTerminate() { super.onTerminate(); diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/SearchAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/SearchAdapter.java index b4490349..225423bf 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/SearchAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/SearchAdapter.java @@ -10,20 +10,16 @@ import android.view.View; import android.view.ViewGroup; import com.bumptech.glide.Glide; -import com.bumptech.glide.Priority; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.request.target.Target; import com.kabouzeid.appthemehelper.util.ATHUtil; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; +import com.kabouzeid.gramophone.glide.ArtistGlideRequest; import com.kabouzeid.gramophone.glide.SongGlideRequest; -import com.kabouzeid.gramophone.glide.artistimage.ArtistImage; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.menu.SongMenuHelper; import com.kabouzeid.gramophone.model.Album; import com.kabouzeid.gramophone.model.Artist; import com.kabouzeid.gramophone.model.Song; -import com.kabouzeid.gramophone.util.ArtistSignatureUtil; import com.kabouzeid.gramophone.util.MusicUtil; import com.kabouzeid.gramophone.util.NavigationUtil; @@ -85,15 +81,8 @@ public class SearchAdapter extends RecyclerView.Adapter build() { + //noinspection unchecked + return createBaseRequest(requestManager, artist, noCustomImage, forceDownload) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .signature(createSignature(artist)); + } + } + + public static class BitmapBuilder { + private final Builder builder; + + public BitmapBuilder(Builder builder) { + this.builder = builder; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) + .asBitmap() + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .signature(createSignature(builder.artist)); + } + } + + public static class PaletteBuilder { + final Context context; + private final Builder builder; + + public PaletteBuilder(Builder builder, Context context) { + this.builder = builder; + this.context = context; + } + + public BitmapRequestBuilder build() { + //noinspection unchecked + return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) + .asBitmap() + .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) + .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) + .error(DEFAULT_ERROR_IMAGE) + .animate(DEFAULT_ANIMATION) + .priority(Priority.LOW) + .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + .signature(createSignature(builder.artist)); + } + } + + public static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist, boolean noCustomImage, boolean forceDownload) { + boolean hasCustomImage = CustomArtistImageUtil.getInstance(App.getInstance()).hasCustomArtistImage(artist); + if (noCustomImage || !hasCustomImage) { + return requestManager.load(new ArtistImage(artist.getName(), forceDownload)); + } else { + return requestManager.load(CustomArtistImageUtil.getFile(artist)); + } + } + + public static Key createSignature(Artist artist) { + return ArtistSignatureUtil.getInstance(App.getInstance()).getArtistSignature(artist.getName()); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java index a63fe2c7..f1200211 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java @@ -25,9 +25,6 @@ import com.afollestad.materialcab.MaterialCab; import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.util.DialogUtils; import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.target.Target; import com.github.ksoichiro.android.observablescrollview.ObservableListView; import com.kabouzeid.appthemehelper.util.ColorUtil; import com.kabouzeid.appthemehelper.util.MaterialValueHelper; @@ -36,10 +33,8 @@ import com.kabouzeid.gramophone.adapter.album.HorizontalAlbumAdapter; import com.kabouzeid.gramophone.adapter.song.ArtistSongAdapter; import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; import com.kabouzeid.gramophone.dialogs.SleepTimerDialog; +import com.kabouzeid.gramophone.glide.ArtistGlideRequest; import com.kabouzeid.gramophone.glide.PhonographColoredTarget; -import com.kabouzeid.gramophone.glide.artistimage.ArtistImage; -import com.kabouzeid.gramophone.glide.palette.BitmapPaletteTranscoder; -import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.interfaces.LoaderIds; @@ -52,15 +47,14 @@ import com.kabouzeid.gramophone.misc.WrappedAsyncTaskLoader; import com.kabouzeid.gramophone.model.Artist; import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.ui.activities.base.AbsSlidingMusicPanelActivity; -import com.kabouzeid.gramophone.util.ArtistSignatureUtil; +import com.kabouzeid.gramophone.util.CustomArtistImageUtil; import com.kabouzeid.gramophone.util.NavigationUtil; import com.kabouzeid.gramophone.util.PhonographColorUtil; import com.kabouzeid.gramophone.util.PreferenceUtil; import com.kabouzeid.gramophone.util.Util; -import java.util.Locale; - import java.util.ArrayList; +import java.util.Locale; import butterknife.BindView; import butterknife.ButterKnife; @@ -75,6 +69,7 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement public static final String TAG = ArtistDetailActivity.class.getSimpleName(); private static final int LOADER_ID = LoaderIds.ARTIST_DETAIL_ACTIVITY; + private static final int REQUEST_CODE_SELECT_IMAGE = 1000; public static final String EXTRA_ARTIST_ID = "extra_artist_id"; @@ -109,6 +104,8 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement private LastFMRestClient lastFMRestClient; + private boolean forceDownload; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -269,49 +266,34 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement }); } - private void loadArtistImage(final boolean forceDownload) { - if (forceDownload) { - ArtistSignatureUtil.getInstance(this).updateArtistSignature(getArtist().getName()); - } - Glide.with(this) - .load(new ArtistImage(getArtist().getName(), forceDownload)) - .asBitmap() - .transcode(new BitmapPaletteTranscoder(this), BitmapPaletteWrapper.class) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) - .placeholder(R.drawable.default_artist_image) - .signature(ArtistSignatureUtil.getInstance(this).getArtistSignature(getArtist().getName())) + private void loadArtistImage() { + ArtistGlideRequest.Builder.from(Glide.with(this), artist) + .forceDownload(forceDownload) + .generatePalette(this).build() .dontAnimate() - .override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .listener(new RequestListener() { - @Override - public boolean onException(@Nullable Exception e, ArtistImage model, Target target, boolean isFirstResource) { - if (forceDownload) { - Toast.makeText(ArtistDetailActivity.this, e != null ? e.getClass().getSimpleName() : "Error", Toast.LENGTH_SHORT).show(); - } - return false; - } - - @Override - public boolean onResourceReady(BitmapPaletteWrapper resource, ArtistImage model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { - if (forceDownload) { - Toast.makeText(ArtistDetailActivity.this, getString(R.string.updated_artist_image), Toast.LENGTH_SHORT).show(); - } - return false; - } - }) .into(new PhonographColoredTarget(artistImage) { @Override public void onColorReady(int color) { setColors(color); } }); + forceDownload = false; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (resultCode == RESULT_OK) { - reload(); + switch (requestCode) { + case REQUEST_CODE_SELECT_IMAGE: + if (resultCode == RESULT_OK) { + CustomArtistImageUtil.getInstance(this).setCustomArtistImage(artist, data.getData()); + } + break; + default: + if (resultCode == RESULT_OK) { + reload(); + } + break; } } @@ -387,9 +369,15 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement loadBiography(); } return true; - case R.id.action_re_download_artist_image: + case R.id.action_set_artist_image: + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("image/*"); + startActivityForResult(Intent.createChooser(intent, getString(R.string.pick_from_local_storage)), REQUEST_CODE_SELECT_IMAGE); + return true; + case R.id.action_reset_artist_image: Toast.makeText(ArtistDetailActivity.this, getResources().getString(R.string.updating), Toast.LENGTH_SHORT).show(); - loadArtistImage(true); + CustomArtistImageUtil.getInstance(ArtistDetailActivity.this).resetCustomArtistImage(artist); + forceDownload = true; return true; case R.id.action_colored_footers: item.setChecked(!item.isChecked()); @@ -451,7 +439,7 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement private void setArtist(Artist artist) { this.artist = artist; - loadArtistImage(false); + loadArtistImage(); if (Util.isAllowedToDownloadMetadata(this)) { loadBiography(); diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AbsTagEditorActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AbsTagEditorActivity.java index cf9ab563..ab0f4fd1 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AbsTagEditorActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AbsTagEditorActivity.java @@ -64,7 +64,7 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { public static final String EXTRA_ID = "extra_id"; public static final String EXTRA_PALETTE = "extra_palette"; private static final String TAG = AbsTagEditorActivity.class.getSimpleName(); - private static final int REQUEST_CODE_SELECT_IMAGE = 1337; + private static final int REQUEST_CODE_SELECT_IMAGE = 1000; @BindView(R.id.play_pause_fab) FloatingActionButton fab; @BindView(R.id.observableScrollView) @@ -429,6 +429,7 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { Uri selectedImage = imageReturnedIntent.getData(); loadImageFromFile(selectedImage); } + break; } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AlbumTagEditorActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AlbumTagEditorActivity.java index ef8ec723..f1f46893 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AlbumTagEditorActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AlbumTagEditorActivity.java @@ -26,6 +26,7 @@ import com.kabouzeid.gramophone.lastfm.rest.LastFMRestClient; import com.kabouzeid.gramophone.lastfm.rest.model.LastFmAlbum; import com.kabouzeid.gramophone.loader.AlbumLoader; import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.util.ImageUtil; import com.kabouzeid.gramophone.util.LastFMUtil; import com.kabouzeid.gramophone.util.PhonographColorUtil; @@ -123,7 +124,7 @@ public class AlbumTagEditorActivity extends AbsTagEditorActivity implements Text @Override public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation glideAnimation) { - albumArtBitmap = getResizedAlbumCover(resource.getBitmap(), 2048); + albumArtBitmap = ImageUtil.resizeBitmap(resource.getBitmap(), 2048); setImageBitmap(albumArtBitmap, PhonographColorUtil.getColor(resource.getPalette(), ATHUtil.resolveColor(AlbumTagEditorActivity.this, R.attr.defaultFooterColor))); deleteAlbumArt = false; dataChanged(); @@ -208,7 +209,7 @@ public class AlbumTagEditorActivity extends AbsTagEditorActivity implements Text @Override public void onResourceReady(BitmapPaletteWrapper resource, GlideAnimation glideAnimation) { PhonographColorUtil.getColor(resource.getPalette(), Color.TRANSPARENT); - albumArtBitmap = getResizedAlbumCover(resource.getBitmap(), 2048); + albumArtBitmap = ImageUtil.resizeBitmap(resource.getBitmap(), 2048); setImageBitmap(albumArtBitmap, PhonographColorUtil.getColor(resource.getPalette(), ATHUtil.resolveColor(AlbumTagEditorActivity.this, R.attr.defaultFooterColor))); deleteAlbumArt = false; dataChanged(); @@ -232,32 +233,6 @@ public class AlbumTagEditorActivity extends AbsTagEditorActivity implements Text dataChanged(); } - private static Bitmap getResizedAlbumCover(@NonNull Bitmap src, int maxForSmallerSize) { - int width = src.getWidth(); - int height = src.getHeight(); - - final int dstWidth; - final int dstHeight; - - if (width < height) { - if (maxForSmallerSize >= width) { - return src; - } - float ratio = (float) height / width; - dstWidth = maxForSmallerSize; - dstHeight = Math.round(maxForSmallerSize * ratio); - } else { - if (maxForSmallerSize >= height) { - return src; - } - float ratio = (float) width / height; - dstWidth = Math.round(maxForSmallerSize * ratio); - dstHeight = maxForSmallerSize; - } - - return Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false); - } - @Override protected void setColors(int color) { super.setColors(color); diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/CustomArtistImageUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/CustomArtistImageUtil.java new file mode 100644 index 00000000..d46cfbc5 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/util/CustomArtistImageUtil.java @@ -0,0 +1,132 @@ +package com.kabouzeid.gramophone.util; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; +import com.kabouzeid.gramophone.App; +import com.kabouzeid.gramophone.model.Artist; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Locale; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ + +public class CustomArtistImageUtil { + private static final String CUSTOM_ARTIST_IMAGE_PREFS = "custom_artist_image"; + private static final String FOLDER_NAME = "/custom_artist_images/"; + + private static CustomArtistImageUtil sInstance; + + private final SharedPreferences mPreferences; + + private CustomArtistImageUtil(@NonNull final Context context) { + mPreferences = context.getApplicationContext().getSharedPreferences(CUSTOM_ARTIST_IMAGE_PREFS, Context.MODE_PRIVATE); + } + + public static CustomArtistImageUtil getInstance(@NonNull final Context context) { + if (sInstance == null) { + sInstance = new CustomArtistImageUtil(context.getApplicationContext()); + } + return sInstance; + } + + public void setCustomArtistImage(final Artist artist, Uri uri) { + Glide.with(App.getInstance()) + .load(uri) + .asBitmap() + .diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(true) + .into(new SimpleTarget() { + @Override + public void onLoadFailed(Exception e, Drawable errorDrawable) { + super.onLoadFailed(e, errorDrawable); + e.printStackTrace(); + Toast.makeText(App.getInstance(), e.toString(), Toast.LENGTH_LONG).show(); + } + + @Override + public void onResourceReady(final Bitmap resource, GlideAnimation glideAnimation) { + new AsyncTask() { + @SuppressLint("ApplySharedPref") + @Override + protected Void doInBackground(Void... params) { + File dir = new File(App.getInstance().getFilesDir(), FOLDER_NAME); + if (!dir.exists()) { + if (!dir.mkdirs()) { // create the folder + return null; + } + } + File file = new File(dir, getFileName(artist)); + + boolean succesful = false; + try { + OutputStream os = new BufferedOutputStream(new FileOutputStream(file)); + succesful = ImageUtil.resizeBitmap(resource, 2048).compress(Bitmap.CompressFormat.JPEG, 100, os); + os.close(); + } catch (IOException e) { + Toast.makeText(App.getInstance(), e.toString(), Toast.LENGTH_LONG).show(); + } + + if (succesful) { + mPreferences.edit().putBoolean(getFileName(artist), true).commit(); + ArtistSignatureUtil.getInstance(App.getInstance()).updateArtistSignature(artist.getName()); + App.getInstance().getContentResolver().notifyChange(Uri.parse("content://media"), null); // trigger media store changed to force artist image reload + } + return null; + } + }.execute(); + } + }); + } + + public void resetCustomArtistImage(final Artist artist) { + new AsyncTask() { + @SuppressLint("ApplySharedPref") + @Override + protected Void doInBackground(Void... params) { + mPreferences.edit().putBoolean(getFileName(artist), false).commit(); + ArtistSignatureUtil.getInstance(App.getInstance()).updateArtistSignature(artist.getName()); + App.getInstance().getContentResolver().notifyChange(Uri.parse("content://media"), null); // trigger media store changed to force artist image reload + + File file = getFile(artist); + if (!file.exists()) { + return null; + } else { + file.delete(); + } + return null; + } + }.execute(); + } + + // shared prefs saves us many IO operations + public boolean hasCustomArtistImage(Artist artist) { + return mPreferences.getBoolean(getFileName(artist), false); + } + + private static String getFileName(Artist artist) { + return String.format(Locale.US, "#%d#%s.jpeg", artist.getId(), artist.getName()); + } + + public static File getFile(Artist artist) { + File dir = new File(App.getInstance().getFilesDir(), FOLDER_NAME); + return new File(dir, getFileName(artist)); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/ImageUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/ImageUtil.java index 19406bf2..d3f32bff 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/ImageUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/ImageUtil.java @@ -1,5 +1,8 @@ package com.kabouzeid.gramophone.util; +import android.graphics.Bitmap; +import android.support.annotation.NonNull; + /** * @author Karim Abou Zeid (kabouzeid) */ @@ -29,4 +32,30 @@ public class ImageUtil { return inSampleSize; } + + public static Bitmap resizeBitmap(@NonNull Bitmap src, int maxForSmallerSize) { + int width = src.getWidth(); + int height = src.getHeight(); + + final int dstWidth; + final int dstHeight; + + if (width < height) { + if (maxForSmallerSize >= width) { + return src; + } + float ratio = (float) height / width; + dstWidth = maxForSmallerSize; + dstHeight = Math.round(maxForSmallerSize * ratio); + } else { + if (maxForSmallerSize >= height) { + return src; + } + float ratio = (float) width / height; + dstWidth = Math.round(maxForSmallerSize * ratio); + dstHeight = maxForSmallerSize; + } + + return Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false); + } } diff --git a/app/src/main/res/menu/menu_artist_detail.xml b/app/src/main/res/menu/menu_artist_detail.xml index 8f54b02d..2493cb44 100644 --- a/app/src/main/res/menu/menu_artist_detail.xml +++ b/app/src/main/res/menu/menu_artist_detail.xml @@ -30,8 +30,13 @@ app:showAsAction="never" /> + + Clear blacklist Do you want to clear the blacklist? The content of blacklisted folders is hidden from your library. + Reset artist image + Set artist image