feat: replace artist image by a mosaic of covers

This commit is contained in:
Matthieu 2019-06-21 14:56:12 +02:00
commit dec0832b6e
10 changed files with 282 additions and 167 deletions

View file

@ -4,6 +4,9 @@ import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.List;
import com.bumptech.glide.BitmapRequestBuilder; import com.bumptech.glide.BitmapRequestBuilder;
import com.bumptech.glide.DrawableRequestBuilder; import com.bumptech.glide.DrawableRequestBuilder;
import com.bumptech.glide.DrawableTypeRequest; import com.bumptech.glide.DrawableTypeRequest;
@ -15,10 +18,13 @@ import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.Target;
import com.kabouzeid.gramophone.App; import com.kabouzeid.gramophone.App;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.glide.artistimage.AlbumCover;
import com.kabouzeid.gramophone.glide.artistimage.ArtistImage; import com.kabouzeid.gramophone.glide.artistimage.ArtistImage;
import com.kabouzeid.gramophone.glide.palette.BitmapPaletteTranscoder; import com.kabouzeid.gramophone.glide.palette.BitmapPaletteTranscoder;
import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper;
import com.kabouzeid.gramophone.model.Album;
import com.kabouzeid.gramophone.model.Artist; import com.kabouzeid.gramophone.model.Artist;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.util.ArtistSignatureUtil; import com.kabouzeid.gramophone.util.ArtistSignatureUtil;
import com.kabouzeid.gramophone.util.CustomArtistImageUtil; import com.kabouzeid.gramophone.util.CustomArtistImageUtil;
@ -35,7 +41,6 @@ public class ArtistGlideRequest {
final RequestManager requestManager; final RequestManager requestManager;
final Artist artist; final Artist artist;
boolean noCustomImage; boolean noCustomImage;
boolean forceDownload;
public static Builder from(@NonNull RequestManager requestManager, Artist artist) { public static Builder from(@NonNull RequestManager requestManager, Artist artist) {
return new Builder(requestManager, artist); return new Builder(requestManager, artist);
@ -59,14 +64,9 @@ public class ArtistGlideRequest {
return this; return this;
} }
public Builder forceDownload(boolean forceDownload) {
this.forceDownload = forceDownload;
return this;
}
public DrawableRequestBuilder<GlideDrawable> build() { public DrawableRequestBuilder<GlideDrawable> build() {
//noinspection unchecked //noinspection unchecked
return createBaseRequest(requestManager, artist, noCustomImage, forceDownload) return createBaseRequest(requestManager, artist, noCustomImage)
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.error(DEFAULT_ERROR_IMAGE) .error(DEFAULT_ERROR_IMAGE)
.animate(DEFAULT_ANIMATION) .animate(DEFAULT_ANIMATION)
@ -85,7 +85,7 @@ public class ArtistGlideRequest {
public BitmapRequestBuilder<?, Bitmap> build() { public BitmapRequestBuilder<?, Bitmap> build() {
//noinspection unchecked //noinspection unchecked
return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage)
.asBitmap() .asBitmap()
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.error(DEFAULT_ERROR_IMAGE) .error(DEFAULT_ERROR_IMAGE)
@ -107,7 +107,7 @@ public class ArtistGlideRequest {
public BitmapRequestBuilder<?, BitmapPaletteWrapper> build() { public BitmapRequestBuilder<?, BitmapPaletteWrapper> build() {
//noinspection unchecked //noinspection unchecked
return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload) return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage)
.asBitmap() .asBitmap()
.transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class) .transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class)
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
@ -119,10 +119,15 @@ public class ArtistGlideRequest {
} }
} }
public static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist, boolean noCustomImage, boolean forceDownload) { public static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist, boolean noCustomImage) {
boolean hasCustomImage = CustomArtistImageUtil.getInstance(App.getInstance()).hasCustomArtistImage(artist); boolean hasCustomImage = CustomArtistImageUtil.getInstance(App.getInstance()).hasCustomArtistImage(artist);
if (noCustomImage || !hasCustomImage) { if (noCustomImage || !hasCustomImage) {
return requestManager.load(new ArtistImage(artist.getName(), forceDownload)); final List<AlbumCover> songs = new ArrayList<>();
for (final Album album : artist.albums) {
final Song song = album.safeGetFirstSong();
songs.add(new AlbumCover(album.getYear(), song.data));
}
return requestManager.load(new ArtistImage(artist.getName(), songs));
} else { } else {
return requestManager.load(CustomArtistImageUtil.getFile(artist)); return requestManager.load(CustomArtistImageUtil.getFile(artist));
} }

View file

@ -2,6 +2,8 @@ package com.kabouzeid.gramophone.glide;
import android.content.Context; import android.content.Context;
import java.io.InputStream;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder; import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.module.GlideModule; import com.bumptech.glide.module.GlideModule;
@ -10,8 +12,6 @@ import com.kabouzeid.gramophone.glide.artistimage.ArtistImageLoader;
import com.kabouzeid.gramophone.glide.audiocover.AudioFileCover; import com.kabouzeid.gramophone.glide.audiocover.AudioFileCover;
import com.kabouzeid.gramophone.glide.audiocover.AudioFileCoverLoader; import com.kabouzeid.gramophone.glide.audiocover.AudioFileCoverLoader;
import java.io.InputStream;
/** /**
* @author Karim Abou Zeid (kabouzeid) * @author Karim Abou Zeid (kabouzeid)
*/ */
@ -24,6 +24,6 @@ public class PhonographGlideModule implements GlideModule {
@Override @Override
public void registerComponents(Context context, Glide glide) { public void registerComponents(Context context, Glide glide) {
glide.register(AudioFileCover.class, InputStream.class, new AudioFileCoverLoader.Factory()); glide.register(AudioFileCover.class, InputStream.class, new AudioFileCoverLoader.Factory());
glide.register(ArtistImage.class, InputStream.class, new ArtistImageLoader.Factory(context)); glide.register(ArtistImage.class, InputStream.class, new ArtistImageLoader.Factory());
} }
} }

View file

@ -0,0 +1,37 @@
package com.kabouzeid.gramophone.glide.artistimage;
/**
* Used to define the artist cover
*/
public class AlbumCover {
private int year;
private String filePath;
public AlbumCover(int year, String filePath) {
this.filePath = filePath;
this.year = year;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
}

View file

@ -1,14 +1,19 @@
package com.kabouzeid.gramophone.glide.artistimage; package com.kabouzeid.gramophone.glide.artistimage;
import java.util.List;
/** /**
* @author Karim Abou Zeid (kabouzeid) * @author Karim Abou Zeid (kabouzeid)
*/ */
public class ArtistImage { public class ArtistImage {
public final String artistName; public final String artistName;
public final boolean skipOkHttpCache;
public ArtistImage(String artistName, boolean skipOkHttpCache) { // filePath to get the image of the artist
public final List<AlbumCover> albumCovers;
public ArtistImage(String artistName, final List<AlbumCover> albumCovers) {
this.artistName = artistName; this.artistName = artistName;
this.skipOkHttpCache = skipOkHttpCache; this.albumCovers = albumCovers;
} }
} }

View file

@ -1,43 +1,36 @@
package com.kabouzeid.gramophone.glide.artistimage; package com.kabouzeid.gramophone.glide.artistimage;
import android.content.Context; import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.media.MediaMetadataRetriever;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.bumptech.glide.Priority; import com.bumptech.glide.Priority;
import com.bumptech.glide.load.data.DataFetcher; import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl; import com.kabouzeid.gramophone.glide.audiocover.AudioFileCoverUtils;
import com.bumptech.glide.load.model.ModelLoader; import com.kabouzeid.gramophone.util.ImageUtil;
import com.kabouzeid.gramophone.lastfm.rest.LastFMRestClient;
import com.kabouzeid.gramophone.lastfm.rest.model.LastFmArtist;
import com.kabouzeid.gramophone.util.LastFMUtil;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.PreferenceUtil;
import java.io.IOException;
import java.io.InputStream;
import retrofit2.Response;
/** /**
* @author Karim Abou Zeid (kabouzeid) * @author Karim Abou Zeid (kabouzeid)
*/ */
public class ArtistImageFetcher implements DataFetcher<InputStream> { public class ArtistImageFetcher implements DataFetcher<InputStream> {
private Context context;
private final LastFMRestClient lastFMRestClient;
private final ArtistImage model; private final ArtistImage model;
private ModelLoader<GlideUrl, InputStream> urlLoader;
private final int width;
private final int height;
private volatile boolean isCancelled;
private DataFetcher<InputStream> urlFetcher;
public ArtistImageFetcher(Context context, LastFMRestClient lastFMRestClient, ArtistImage model, ModelLoader<GlideUrl, InputStream> urlLoader, int width, int height) { private InputStream stream;
this.context = context;
this.lastFMRestClient = lastFMRestClient; public ArtistImageFetcher(final ArtistImage model) {
this.model = model; this.model = model;
this.urlLoader = urlLoader;
this.width = width;
this.height = height;
} }
@Override @Override
@ -48,37 +41,126 @@ public class ArtistImageFetcher implements DataFetcher<InputStream> {
@Override @Override
public InputStream loadData(Priority priority) throws Exception { public InputStream loadData(Priority priority) throws Exception {
if (!MusicUtil.isArtistNameUnknown(model.artistName) && PreferenceUtil.isAllowedToDownloadMetadata(context)) {
Response<LastFmArtist> response = lastFMRestClient.getApiService().getArtistInfo(model.artistName, null, model.skipOkHttpCache ? "no-cache" : null).execute();
if (!response.isSuccessful()) { return stream = getMosaic(model.albumCovers);
throw new IOException("Request failed with code: " + response.code()); }
private InputStream getMosaic(final List<AlbumCover> albumCovers) throws FileNotFoundException {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
int artistBitMapSize = 512;
final Map<InputStream, Integer> images = new HashMap<>();
InputStream result = null;
List<InputStream> streams = new ArrayList<>();
try {
for (final AlbumCover cover : albumCovers) {
retriever.setDataSource(cover.getFilePath());
byte[] picture = retriever.getEmbeddedPicture();
final InputStream stream;
if (picture != null) {
stream = new ByteArrayInputStream(picture);
} else {
stream = AudioFileCoverUtils.fallback(cover.getFilePath());
}
if (stream != null) {
images.put(stream, cover.getYear());
}
} }
LastFmArtist lastFmArtist = response.body(); int nbImages = images.size();
if (isCancelled) return null; if (nbImages > 3) {
streams = new ArrayList<>(images.keySet());
GlideUrl url = new GlideUrl(LastFMUtil.getLargestArtistImageUrl(lastFmArtist.getArtist().getImage())); int divisor = 1;
urlFetcher = urlLoader.getResourceFetcher(url, width, height); for (int i = 1; i < nbImages && Math.pow(i, 2) <= nbImages; ++i) {
divisor = i;
}
divisor += 1;
double nbTiles = Math.pow(divisor, 2);
if (nbImages < nbTiles) {
divisor -= 1;
nbTiles = Math.pow(divisor, 2);
}
final int resize = (artistBitMapSize / divisor) + 1;
final Bitmap bitmap = Bitmap.createBitmap(artistBitMapSize, artistBitMapSize, Bitmap.Config.RGB_565);
final Canvas canvas = new Canvas(bitmap);
int x = 0;
int y = 0;
for (int i = 0; i < streams.size() && i < nbTiles; ++i) {
final Bitmap bitmap1 = ImageUtil.resize(streams.get(i), resize, resize);
canvas.drawBitmap(bitmap1, x, y, null);
x += resize;
if (x >= artistBitMapSize) {
x = 0;
y += resize;
}
}
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
result = new ByteArrayInputStream(bos.toByteArray());
} else if (nbImages > 0) {
// we return the last cover album of the artist
Map.Entry<InputStream, Integer> maxEntryYear = null;
for (final Map.Entry<InputStream, Integer> entry : images.entrySet()) {
if (maxEntryYear == null || entry.getValue()
.compareTo(maxEntryYear.getValue()) > 0) {
maxEntryYear = entry;
}
}
if (maxEntryYear != null) {
result = maxEntryYear.getKey();
} else {
result = images.entrySet()
.iterator()
.next()
.getKey();
}
}
} finally {
retriever.release();
try {
for (final InputStream stream : streams) {
stream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return urlFetcher.loadData(priority);
} }
return null; return result;
} }
@Override @Override
public void cleanup() { public void cleanup() {
if (urlFetcher != null) { // already cleaned up in loadData and ByteArrayInputStream will be GC'd
urlFetcher.cleanup(); if (stream != null) {
try {
stream.close();
} catch (IOException ignore) {
// can't do much about it
}
} }
} }
@Override @Override
public void cancel() { public void cancel() {
isCancelled = true;
if (urlFetcher != null) {
urlFetcher.cancel();
}
} }
} }

View file

@ -2,68 +2,37 @@ package com.kabouzeid.gramophone.glide.artistimage;
import android.content.Context; import android.content.Context;
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader; import java.io.InputStream;
import com.bumptech.glide.load.data.DataFetcher; import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GenericLoaderFactory; import com.bumptech.glide.load.model.GenericLoaderFactory;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader; import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory; import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.stream.StreamModelLoader; import com.bumptech.glide.load.model.stream.StreamModelLoader;
import com.kabouzeid.gramophone.lastfm.rest.LastFMRestClient;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
/** /**
* @author Karim Abou Zeid (kabouzeid) * @author Karim Abou Zeid (kabouzeid)
*/ */
public class ArtistImageLoader implements StreamModelLoader<ArtistImage> { public class ArtistImageLoader implements StreamModelLoader<ArtistImage> {
// we need these very low values to make sure our artist image loading calls doesn't block the image loading queue
private static final int TIMEOUT = 700;
private Context context;
private LastFMRestClient lastFMClient;
private ModelLoader<GlideUrl, InputStream> urlLoader;
public ArtistImageLoader(Context context, LastFMRestClient lastFMRestClient, ModelLoader<GlideUrl, InputStream> urlLoader) {
this.context = context;
this.lastFMClient = lastFMRestClient;
this.urlLoader = urlLoader;
}
@Override @Override
public DataFetcher<InputStream> getResourceFetcher(ArtistImage model, int width, int height) { public DataFetcher<InputStream> getResourceFetcher(final ArtistImage model, int width, int height) {
return new ArtistImageFetcher(context, lastFMClient, model, urlLoader, width, height);
return new ArtistImageFetcher(model);
} }
public static class Factory implements ModelLoaderFactory<ArtistImage, InputStream> { public static class Factory implements ModelLoaderFactory<ArtistImage, InputStream> {
private LastFMRestClient lastFMClient;
private OkHttpUrlLoader.Factory okHttpFactory;
public Factory(Context context) {
okHttpFactory = new OkHttpUrlLoader.Factory(new OkHttpClient.Builder()
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.build());
lastFMClient = new LastFMRestClient(LastFMRestClient.createDefaultOkHttpClientBuilder(context)
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.build());
}
@Override @Override
public ModelLoader<ArtistImage, InputStream> build(Context context, GenericLoaderFactory factories) { public ModelLoader<ArtistImage, InputStream> build(Context context, GenericLoaderFactory factories) {
return new ArtistImageLoader(context, lastFMClient, okHttpFactory.build(context, factories));
return new ArtistImageLoader();
} }
@Override @Override
public void teardown() { public void teardown() {
okHttpFactory.teardown();
} }
} }
} }

View file

@ -2,30 +2,23 @@ package com.kabouzeid.gramophone.glide.audiocover;
import android.media.MediaMetadataRetriever; import android.media.MediaMetadataRetriever;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.data.DataFetcher;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.images.Artwork;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.data.DataFetcher;
/** /**
* @author Karim Abou Zeid (kabouzeid) * @author Karim Abou Zeid (kabouzeid)
*/ */
public class AudioFileCoverFetcher implements DataFetcher<InputStream> { public class AudioFileCoverFetcher implements DataFetcher<InputStream> {
private final AudioFileCover model; private final AudioFileCover model;
private FileInputStream stream;
private InputStream stream;
public AudioFileCoverFetcher(AudioFileCover model) { public AudioFileCoverFetcher(AudioFileCover model) {
this.model = model; this.model = model;
} }
@ -36,50 +29,22 @@ public class AudioFileCoverFetcher implements DataFetcher<InputStream> {
} }
@Override @Override
public InputStream loadData(Priority priority) throws Exception { public InputStream loadData(final Priority priority) throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
final MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try { try {
retriever.setDataSource(model.filePath); retriever.setDataSource(model.filePath);
byte[] picture = retriever.getEmbeddedPicture(); byte[] picture = retriever.getEmbeddedPicture();
if (picture != null) { if (picture != null) {
return new ByteArrayInputStream(picture); stream = new ByteArrayInputStream(picture);
} else { } else {
return fallback(model.filePath); stream = AudioFileCoverUtils.fallback(model.filePath);
} }
} finally { } finally {
retriever.release(); retriever.release();
} }
}
private static final String[] FALLBACKS = {"cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png"}; return stream;
private InputStream fallback(String path) throws FileNotFoundException {
// Method 1: use embedded high resolution album art if there is any
try {
MP3File mp3File = new MP3File(path);
if (mp3File.hasID3v2Tag()) {
Artwork art = mp3File.getTag().getFirstArtwork();
if (art != null) {
byte[] imageData = art.getBinaryData();
return new ByteArrayInputStream(imageData);
}
}
// If there are any exceptions, we ignore them and continue to the other fallback method
} catch (ReadOnlyFileException ignored) {
} catch (InvalidAudioFrameException ignored) {
} catch (TagException ignored) {
} catch (IOException ignored) {
}
// Method 2: look for album art in external files
File parent = new File(path).getParentFile();
for (String fallback : FALLBACKS) {
File cover = new File(parent, fallback);
if (cover.exists()) {
return stream = new FileInputStream(cover);
}
}
return null;
} }
@Override @Override

View file

@ -0,0 +1,49 @@
package com.kabouzeid.gramophone.glide.audiocover;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.images.Artwork;
public class AudioFileCoverUtils {
public static final String[] FALLBACKS = {"cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png"};
public static InputStream fallback(String path) throws FileNotFoundException {
// Method 1: use embedded high resolution album art if there is any
try {
MP3File mp3File = new MP3File(path);
if (mp3File.hasID3v2Tag()) {
Artwork art = mp3File.getTag().getFirstArtwork();
if (art != null) {
byte[] imageData = art.getBinaryData();
return new ByteArrayInputStream(imageData);
}
}
// If there are any exceptions, we ignore them and continue to the other fallback method
} catch (ReadOnlyFileException ignored) {
} catch (InvalidAudioFrameException ignored) {
} catch (TagException ignored) {
} catch (IOException ignored) {
}
// Method 2: look for album art in external files
final File parent = new File(path).getParentFile();
for (String fallback : FALLBACKS) {
File cover = new File(parent, fallback);
if (cover.exists()) {
return new FileInputStream(cover);
}
}
return null;
}
}

View file

@ -4,13 +4,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.appcompat.widget.Toolbar;
import android.text.Html; import android.text.Html;
import android.text.Spanned; import android.text.Spanned;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -20,6 +13,21 @@ import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import java.util.List;
import java.util.Locale;
import com.afollestad.materialcab.MaterialCab; import com.afollestad.materialcab.MaterialCab;
import com.afollestad.materialdialogs.MaterialDialog; import com.afollestad.materialdialogs.MaterialDialog;
@ -53,16 +61,6 @@ import com.kabouzeid.gramophone.util.NavigationUtil;
import com.kabouzeid.gramophone.util.PhonographColorUtil; import com.kabouzeid.gramophone.util.PhonographColorUtil;
import com.kabouzeid.gramophone.util.PreferenceUtil; import com.kabouzeid.gramophone.util.PreferenceUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import butterknife.BindView;
import butterknife.ButterKnife;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/** /**
* Be careful when changing things in this Activity! * Be careful when changing things in this Activity!
*/ */
@ -113,7 +111,6 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement
private LastFMRestClient lastFMRestClient; private LastFMRestClient lastFMRestClient;
private boolean forceDownload;
private final SimpleObservableScrollViewCallbacks observableScrollViewCallbacks = new SimpleObservableScrollViewCallbacks() { private final SimpleObservableScrollViewCallbacks observableScrollViewCallbacks = new SimpleObservableScrollViewCallbacks() {
@Override @Override
public void onScrollChanged(int scrollY, boolean b, boolean b2) { public void onScrollChanged(int scrollY, boolean b, boolean b2) {
@ -254,7 +251,6 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement
private void loadArtistImage() { private void loadArtistImage() {
ArtistGlideRequest.Builder.from(Glide.with(this), artist) ArtistGlideRequest.Builder.from(Glide.with(this), artist)
.forceDownload(forceDownload)
.generatePalette(this).build() .generatePalette(this).build()
.dontAnimate() .dontAnimate()
.into(new PhonographColoredTarget(artistImage) { .into(new PhonographColoredTarget(artistImage) {
@ -263,7 +259,6 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement
setColors(color); setColors(color);
} }
}); });
forceDownload = false;
} }
@Override @Override
@ -375,7 +370,6 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement
case R.id.action_reset_artist_image: case R.id.action_reset_artist_image:
Toast.makeText(ArtistDetailActivity.this, getResources().getString(R.string.updating), Toast.LENGTH_SHORT).show(); Toast.makeText(ArtistDetailActivity.this, getResources().getString(R.string.updating), Toast.LENGTH_SHORT).show();
CustomArtistImageUtil.getInstance(ArtistDetailActivity.this).resetCustomArtistImage(artist); CustomArtistImageUtil.getInstance(ArtistDetailActivity.this).resetCustomArtistImage(artist);
forceDownload = true;
return true; return true;
case R.id.action_colored_footers: case R.id.action_colored_footers:
item.setChecked(!item.isChecked()); item.setChecked(!item.isChecked());

View file

@ -4,6 +4,7 @@ import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
@ -14,6 +15,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import java.io.InputStream;
import com.kabouzeid.appthemehelper.util.TintHelper; import com.kabouzeid.appthemehelper.util.TintHelper;
/** /**
@ -109,4 +112,10 @@ public class ImageUtil {
a.recycle(); a.recycle();
return drawable; return drawable;
} }
public static Bitmap resize(InputStream stream, int scaledWidth, int scaledHeight) {
final Bitmap bitmap = BitmapFactory.decodeStream(stream);
return Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
}
} }