package com.marverenic.music.viewmodel;
import android.content.Context;
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.databinding.BindingAdapter;
import android.databinding.ObservableInt;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.PopupMenu;
import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import com.marverenic.music.BR;
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.data.store.ThemeStore;
import com.marverenic.music.dialog.AppendPlaylistDialogFragment;
import com.marverenic.music.fragments.BaseFragment;
import com.marverenic.music.model.Song;
import com.marverenic.music.player.PlayerController;
import javax.inject.Inject;
import timber.log.Timber;
public class NowPlayingControllerViewModel extends BaseObservable {
private static final String TAG_PLAYLIST_DIALOG = "AppendPlaylistDialog";
private Context mContext;
private FragmentManager mFragmentManager;
@Inject MusicStore mMusicStore;
@Inject ThemeStore mThemeStore;
@Inject PlayerController mPlayerController;
@Nullable
private Song mSong;
private boolean mPlaying;
private int mDuration;
private boolean mUserTouchingProgressBar;
private Animation mSeekBarThumbAnimation;
private final ObservableInt mSeekbarPosition;
private final ObservableInt mCurrentPositionObservable;
public NowPlayingControllerViewModel(BaseFragment fragment) {
mContext = fragment.getContext();
mFragmentManager = fragment.getFragmentManager();
mCurrentPositionObservable = new ObservableInt();
mSeekbarPosition = new ObservableInt();
JockeyApplication.getComponent(mContext).inject(this);
mPlayerController.getCurrentPosition()
.compose(fragment.bindToLifecycle())
.subscribe(
position -> {
mCurrentPositionObservable.set(position);
if (!mUserTouchingProgressBar) {
mSeekbarPosition.set(position);
}
},
throwable -> {
Timber.e(throwable, "failed to update position");
});
mPlayerController.getNowPlaying()
.compose(fragment.bindToLifecycle())
.subscribe(this::setSong,
throwable -> Timber.e(throwable, "Failed to set song"));
mPlayerController.isPlaying()
.compose(fragment.bindToLifecycle())
.subscribe(this::setPlaying,
throwable -> Timber.e(throwable, "Failed to set playing"));
mPlayerController.getDuration()
.compose(fragment.bindToLifecycle())
.subscribe(this::setDuration,
throwable -> Timber.e(throwable, "Failed to set duration"));
}
private void setSong(@Nullable Song song) {
mSong = song;
notifyPropertyChanged(BR.songTitle);
notifyPropertyChanged(BR.artistName);
notifyPropertyChanged(BR.albumName);
notifyPropertyChanged(BR.positionVisibility);
notifyPropertyChanged(BR.seekbarEnabled);
}
private void setPlaying(boolean playing) {
mPlaying = playing;
notifyPropertyChanged(BR.togglePlayIcon);
}
private void setDuration(int duration) {
mDuration = duration;
notifyPropertyChanged(BR.songDuration);
}
@Bindable
public String getSongTitle() {
if (mSong == null) {
return mContext.getResources().getString(R.string.nothing_playing);
} else {
return mSong.getSongName();
}
}
@Bindable
public String getArtistName() {
if (mSong == null) {
return mContext.getResources().getString(R.string.unknown_artist);
} else {
return mSong.getArtistName();
}
}
@Bindable
public String getAlbumName() {
if (mSong == null) {
return mContext.getString(R.string.unknown_album);
} else {
return mSong.getAlbumName();
}
}
@Bindable
public int getSongDuration() {
return mDuration;
}
@Bindable
public boolean getSeekbarEnabled() {
return mSong != null;
}
@Bindable
public Drawable getTogglePlayIcon() {
if (mPlaying) {
return ContextCompat.getDrawable(mContext, R.drawable.ic_pause_36dp);
} else {
return ContextCompat.getDrawable(mContext, R.drawable.ic_play_arrow_36dp);
}
}
public ObservableInt getSeekBarPosition() {
return mSeekbarPosition;
}
public ObservableInt getCurrentPosition() {
return mCurrentPositionObservable;
}
@Bindable
public int getPositionVisibility() {
if (mSong == null) {
return View.INVISIBLE;
} else {
return View.VISIBLE;
}
}
@ColorInt
public int getSeekBarHeadTint() {
return mThemeStore.getAccentColor();
}
@Bindable
public int getSeekBarHeadVisibility() {
if (mUserTouchingProgressBar) {
return View.VISIBLE;
} else {
return View.INVISIBLE;
}
}
@Bindable
public Animation getSeekBarHeadAnimation() {
Animation animation = mSeekBarThumbAnimation;
mSeekBarThumbAnimation = null;
return animation;
}
private void animateSeekBarHeadOut() {
mSeekBarThumbAnimation = AnimationUtils.loadAnimation(mContext, R.anim.slider_thumb_out);
mSeekBarThumbAnimation.setInterpolator(mContext, android.R.interpolator.accelerate_quint);
notifyPropertyChanged(BR.seekBarHeadAnimation);
long duration = mSeekBarThumbAnimation.getDuration();
new Handler().postDelayed(() -> notifyPropertyChanged(BR.seekBarHeadVisibility), duration);
}
private void animateSeekBarHeadIn() {
mSeekBarThumbAnimation = AnimationUtils.loadAnimation(mContext, R.anim.slider_thumb_in);
mSeekBarThumbAnimation.setInterpolator(mContext, android.R.interpolator.decelerate_quint);
notifyPropertyChanged(BR.seekBarHeadAnimation);
notifyPropertyChanged(BR.seekBarHeadVisibility);
}
@Bindable
public float getSeekBarHeadMarginLeft() {
return mSeekbarPosition.get() / (float) getSongDuration();
}
public View.OnClickListener onMoreInfoClick() {
return v -> {
if (mSong == null) {
return;
}
PopupMenu menu = new PopupMenu(mContext, v, Gravity.END);
menu.inflate(mSong.isInLibrary()
? R.menu.instance_song_now_playing
: R.menu.instance_song_now_playing_remote);
menu.setOnMenuItemClickListener(onMoreInfoItemClick(mSong));
menu.show();
};
}
private PopupMenu.OnMenuItemClickListener onMoreInfoItemClick(Song song) {
return item -> {
switch (item.getItemId()) {
case R.id.menu_item_navigate_to_artist:
mMusicStore.findArtistById(song.getArtistId()).take(1).subscribe(
artist -> {
mContext.startActivity(ArtistActivity.newIntent(mContext, artist));
},
throwable -> {
Timber.e(throwable, "Failed to find artist");
});
return true;
case R.id.menu_item_navigate_to_album:
mMusicStore.findAlbumById(song.getAlbumId()).take(1).subscribe(
album -> {
mContext.startActivity(AlbumActivity.newIntent(mContext, album));
},
throwable -> {
Timber.e(throwable, "Failed to find album");
});
return true;
case R.id.menu_item_add_to_playlist:
new AppendPlaylistDialogFragment.Builder(mContext, mFragmentManager)
.setSongs(song)
.showSnackbarIn(R.id.now_playing_artwork)
.show(TAG_PLAYLIST_DIALOG);
return true;
}
return false;
};
}
public View.OnClickListener onSkipNextClick() {
return v -> mPlayerController.skip();
}
public View.OnClickListener onSkipBackClick() {
return v -> mPlayerController.previous();
}
public View.OnClickListener onTogglePlayClick() {
return v -> mPlayerController.togglePlay();
}
public OnSeekBarChangeListener onSeek() {
return new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mSeekbarPosition.set(progress);
if (fromUser) {
notifyPropertyChanged(BR.seekBarHeadMarginLeft);
if (!mUserTouchingProgressBar) {
// For keyboards and non-touch based things
onStartTrackingTouch(seekBar);
onStopTrackingTouch(seekBar);
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mUserTouchingProgressBar = true;
animateSeekBarHeadIn();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mUserTouchingProgressBar = false;
animateSeekBarHeadOut();
mPlayerController.seek(seekBar.getProgress());
mCurrentPositionObservable.set(seekBar.getProgress());
}
};
}
@BindingAdapter("onSeekListener")
public static void bindOnSeekListener(SeekBar seekBar, OnSeekBarChangeListener listener) {
seekBar.setOnSeekBarChangeListener(listener);
}
@BindingAdapter("marginLeft_percent")
public static void bindPercentMarginLeft(View view, float percent) {
View parent = (View) view.getParent();
int leftOffset = (int) (parent.getWidth() * percent) - view.getWidth() / 2;
leftOffset = Math.min(leftOffset, parent.getWidth() - view.getWidth());
leftOffset = Math.max(leftOffset, 0);
BindingAdapters.bindLeftMargin(view, leftOffset);
}
}