package com.marverenic.music.viewmodel; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Resources; import android.databinding.BaseObservable; import android.databinding.ObservableField; import android.databinding.ObservableInt; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.support.annotation.ColorInt; import android.support.v4.app.FragmentManager; import android.support.v4.content.res.ResourcesCompat; import android.support.v7.graphics.Palette; import android.support.v7.widget.PopupMenu; import android.view.Gravity; import android.view.View; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.Target; import com.marverenic.music.JockeyApplication; import com.marverenic.music.R; import com.marverenic.music.activity.instance.AlbumActivity; import com.marverenic.music.activity.instance.ArtistActivity; import com.marverenic.music.data.store.MusicStore; import com.marverenic.music.dialog.AppendPlaylistDialogFragment; import com.marverenic.music.model.Album; import com.marverenic.music.player.PlayerController; import com.marverenic.music.view.ViewUtils; import java.io.File; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; import timber.log.Timber; public class AlbumViewModel extends BaseObservable { private static final String TAG_PLAYLIST_DIALOG = "SongViewModel.PlaylistDialog"; @Inject MusicStore mMusicStore; @Inject PlayerController mPlayerController; private Context mContext; private FragmentManager mFragmentManager; private Album mAlbum; private ObservableField<Drawable> mArtistImage; private ObservableInt mTitleTextColor; private ObservableInt mArtistTextColor; private ObservableInt mBackgroundColor; public AlbumViewModel(Context context, FragmentManager fragmentManager) { mContext = context; mFragmentManager = fragmentManager; JockeyApplication.getComponent(mContext).inject(this); } public void setAlbum(Album album) { mAlbum = album; mArtistImage = new ObservableField<>(); mTitleTextColor = new ObservableInt(); mArtistTextColor = new ObservableInt(); mBackgroundColor = new ObservableInt(); defaultColors(); if (mAlbum.getArtUri() != null) { int imageSize = mContext.getResources().getDimensionPixelSize(R.dimen.grid_width); Glide.with(mContext) .load(new File(mAlbum.getArtUri())) .placeholder(R.drawable.art_default) .error(R.drawable.art_default) .listener(new PaletteListener(mTitleTextColor, mArtistTextColor, mBackgroundColor)) .into(new ObservableTarget(imageSize, mArtistImage)); } else { Drawable fallback = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.art_default, mContext.getTheme()); mArtistImage.set(fallback); } notifyChange(); } private void defaultColors() { defaultColors(mContext, mTitleTextColor, mArtistTextColor, mBackgroundColor); } private static void defaultColors(Context context, ObservableInt title, ObservableInt artist, ObservableInt background) { Resources res = context.getResources(); Resources.Theme theme = context.getTheme(); title.set(ResourcesCompat.getColor(res, R.color.grid_text, theme)); artist.set(ResourcesCompat.getColor(res, R.color.grid_detail_text, theme)); background.set(ResourcesCompat.getColor(res, R.color.grid_background_default, theme)); } public String getAlbumTitle() { return mAlbum.getAlbumName(); } public String getAlbumArtist() { return mAlbum.getArtistName(); } public ObservableField<Drawable> getArtistImage() { return mArtistImage; } public ObservableInt getTitleTextColor() { return mTitleTextColor; } public ObservableInt getArtistTextColor() { return mArtistTextColor; } public ObservableInt getBackgroundColor() { return mBackgroundColor; } public View.OnClickListener onClickAlbum() { return v -> mContext.startActivity(AlbumActivity.newIntent(mContext, mAlbum)); } public View.OnClickListener onClickMenu() { return v -> { PopupMenu menu = new android.support.v7.widget.PopupMenu(mContext, v, Gravity.END); menu.inflate(R.menu.instance_album); menu.setOnMenuItemClickListener(onMenuItemClick()); menu.show(); }; } private PopupMenu.OnMenuItemClickListener onMenuItemClick() { return menuItem -> { switch (menuItem.getItemId()) { case R.id.menu_item_queue_item_next: mMusicStore.getSongs(mAlbum).subscribe( mPlayerController::queueNext, throwable -> { Timber.e(throwable, "Failed to get songs"); }); return true; case R.id.menu_item_queue_item_last: mMusicStore.getSongs(mAlbum).subscribe( mPlayerController::queueLast, throwable -> { Timber.e(throwable, "Failed to get songs"); }); return true; case R.id.menu_item_navigate_to_artist: mMusicStore.findArtistById(mAlbum.getArtistId()).subscribe( artist -> { mContext.startActivity(ArtistActivity.newIntent(mContext, artist)); }, throwable -> { Timber.e(throwable, "Failed to find artist"); }); return true; case R.id.menu_item_add_to_playlist: mMusicStore.getSongs(mAlbum).subscribe( songs -> { new AppendPlaylistDialogFragment.Builder(mContext, mFragmentManager) .setSongs(songs, mAlbum.getAlbumName()) .showSnackbarIn(R.id.list) .show(TAG_PLAYLIST_DIALOG); }, throwable -> { Timber.e(throwable, "Failed to get songs"); }); return true; } return false; }; } private static class ObservableTarget extends SimpleTarget<GlideDrawable> { private ObservableField<Drawable> mTarget; public ObservableTarget(int size, ObservableField<Drawable> target) { super(size, size); mTarget = target; } @Override public void onLoadStarted(Drawable placeholder) { mTarget.set(placeholder); } @Override public void onLoadFailed(Exception e, Drawable errorDrawable) { mTarget.set(errorDrawable); Timber.e(e, "failed to load thumbnail"); } @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) { Drawable start = mTarget.get(); if (start != null) { setDrawableWithFade(start, resource); } else { setDrawable(resource); } } private void setDrawableWithFade(Drawable start, Drawable end) { TransitionDrawable transition = new TransitionDrawable(new Drawable[]{start, end}); transition.setCrossFadeEnabled(true); transition.startTransition(300); setDrawable(transition); } private void setDrawable(Drawable resource) { mTarget.set(resource); } } private static class PaletteListener implements RequestListener<File, GlideDrawable> { private static Map<File, Palette.Swatch> sColorMap; private ObservableInt mTitleTextColor; private ObservableInt mArtistTextColor; private ObservableInt mBackgroundColor; static { sColorMap = new HashMap<>(); } public PaletteListener(ObservableInt title, ObservableInt artist, ObservableInt background) { mTitleTextColor = title; mArtistTextColor = artist; mBackgroundColor = background; } @Override public boolean onException(Exception e, File model, Target<GlideDrawable> target, boolean isFirstResource) { return false; } @Override public boolean onResourceReady(GlideDrawable resource, File model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { if (sColorMap.containsKey(model)) { Palette.Swatch swatch = sColorMap.get(model); if (isFromMemoryCache) { setSwatch(swatch); } else { animateSwatch(swatch); } } else { generateSwatch(model, resource); } return false; } private void generateSwatch(File source, Drawable image) { Palette.from(ViewUtils.drawableToBitmap(image)).generate(palette -> { Palette.Swatch swatch = pickSwatch(palette); sColorMap.put(source, swatch); animateSwatch(swatch); }); } private void setSwatch(Palette.Swatch swatch) { if (swatch == null) { return; } mBackgroundColor.set(swatch.getRgb()); mTitleTextColor.set(swatch.getTitleTextColor()); mArtistTextColor.set(swatch.getBodyTextColor()); } private void animateSwatch(Palette.Swatch swatch) { if (swatch == null) { return; } animateColorValue(mBackgroundColor, swatch.getRgb()); animateColorValue(mTitleTextColor, swatch.getTitleTextColor()); animateColorValue(mArtistTextColor, swatch.getBodyTextColor()); } private void animateColorValue(ObservableInt target, @ColorInt int toColor) { ObjectAnimator.ofObject(target, "", new ArgbEvaluator(), target.get(), toColor) .setDuration(300) .start(); } private Palette.Swatch pickSwatch(Palette palette) { if (palette.getVibrantSwatch() != null) { return palette.getVibrantSwatch(); } if (palette.getLightVibrantSwatch() != null) { return palette.getLightVibrantSwatch(); } if (palette.getDarkVibrantSwatch() != null) { return palette.getDarkVibrantSwatch(); } if (palette.getLightMutedSwatch() != null) { return palette.getLightMutedSwatch(); } if (palette.getDarkMutedSwatch() != null) { return palette.getDarkMutedSwatch(); } return null; } } }