/*
* Copyright (C) 2014 Fastboot Mobile, LLC.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, see <http://www.gnu.org/licenses>.
*/
package com.fastbootmobile.encore.app.ui;
import android.animation.Animator;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.v7.graphics.Palette;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.fastbootmobile.encore.app.AlbumActivity;
import com.fastbootmobile.encore.app.PlaybackQueueActivity;
import com.fastbootmobile.encore.app.R;
import com.fastbootmobile.encore.framework.PlaybackProxy;
import com.fastbootmobile.encore.model.Album;
import com.fastbootmobile.encore.model.Artist;
import com.fastbootmobile.encore.model.Playlist;
import com.fastbootmobile.encore.model.SearchResult;
import com.fastbootmobile.encore.model.Song;
import com.fastbootmobile.encore.providers.ILocalCallback;
import com.fastbootmobile.encore.providers.IMusicProvider;
import com.fastbootmobile.encore.providers.ProviderAggregator;
import com.fastbootmobile.encore.service.BasePlaybackCallback;
import com.fastbootmobile.encore.service.PlaybackService;
import com.fastbootmobile.encore.utils.SettingsKeys;
import com.fastbootmobile.encore.utils.Utils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import mbanje.kurt.fabbutton.FabButton;
/**
* ViewGroup for the sticky bottom playing bar
*/
public class PlayingBarView extends RelativeLayout implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "PlayingBarView";
// Delay after which the seek bar is updated (5Hz)
private static final int SEEK_BAR_UPDATE_DELAY = 1000 / 5;
private boolean mIsHiding = false;
private static class PlayingBarHandler extends Handler {
private WeakReference<PlayingBarView> mParent;
public PlayingBarHandler(WeakReference<PlayingBarView> parent) {
mParent = parent;
}
@Override
public void handleMessage(Message msg) {
PlayingBarView parent = mParent.get();
if (parent != null) {
switch (msg.what) {
case MSG_UPDATE_QUEUE:
final int trackLength = PlaybackProxy.getCurrentTrackLength();
if (parent.mPlayFab != null) {
parent.mPlayFab.setMaxProgress(trackLength);
parent.mPlayFab.setEnabled(true);
}
parent.updatePlayingQueue();
break;
case MSG_UPDATE_SEEK:
parent.updateSeekBar();
break;
case MSG_UPDATE_FAB:
parent.updatePlayFab();
break;
}
}
}
}
private ILocalCallback mProviderCallback = new ILocalCallback() {
@Override
public void onSongUpdate(List<Song> s) {
boolean contains = false;
List<Song> playbackQueue = PlaybackProxy.getCurrentPlaybackQueue();
for (Song song : s) {
if (playbackQueue.contains(song)) {
contains = true;
break;
}
}
if (contains) {
mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE);
}
}
@Override
public void onAlbumUpdate(List<Album> a) {
}
@Override
public void onPlaylistUpdate(List<Playlist> p) {
}
@Override
public void onPlaylistRemoved(String ref) {
}
@Override
public void onArtistUpdate(List<Artist> a) {
boolean contains = false;
List<Song> playbackQueue = new ArrayList<>(PlaybackProxy.getCurrentPlaybackQueue());
for (Song song : playbackQueue) {
if (song == null) continue;
for (Artist artist : a) {
if (artist != null && artist.getRef().equals(song.getArtist())) {
contains = true;
break;
}
}
if (contains) {
break;
}
}
if (contains) {
mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE);
}
}
@Override
public void onProviderConnected(IMusicProvider provider) {
}
@Override
public void onSearchResult(List<SearchResult> searchResult) {
}
};
private BasePlaybackCallback mPlaybackCallback = new BasePlaybackCallback() {
@Override
public void onSongStarted(final boolean buffering, Song s) throws RemoteException {
mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE);
mHandler.sendEmptyMessage(MSG_UPDATE_FAB);
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SEEK, SEEK_BAR_UPDATE_DELAY);
}
@Override
public void onPlaybackPause() throws RemoteException {
mHandler.sendEmptyMessage(MSG_UPDATE_FAB);
}
@Override
public void onPlaybackResume() throws RemoteException {
mHandler.sendEmptyMessage(MSG_UPDATE_FAB);
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SEEK, SEEK_BAR_UPDATE_DELAY);
}
@Override
public void onPlaybackQueueChanged() throws RemoteException {
mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE);
}
};
private GestureDetector mGestureDetector;
private BarGestureListener mGestureListener = new BarGestureListener();
private static final int MAX_PEEK_QUEUE_SIZE = 3;
private static final int MSG_UPDATE_QUEUE = 1;
private static final int MSG_UPDATE_SEEK = 2;
private static final int MSG_UPDATE_FAB = 3;
private boolean mIsPlaying;
private LinearLayout mTracksLayout;
private FabButton mPlayFab;
private PlayPauseDrawable mPlayFabDrawable;
private List<Song> mLastQueue;
private PlayingBarHandler mHandler;
private int mAnimationDuration;
private boolean mWrapped;
private boolean mPlayInSeekMode;
private boolean mSkipFabAction;
private boolean mCallbackRegistered = false;
public PlayingBarView(Context context) {
super(context);
init();
}
public PlayingBarView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PlayingBarView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
mAnimationDuration = getResources().getInteger(android.R.integer.config_mediumAnimTime);
mGestureDetector = new GestureDetector(getContext(), mGestureListener);
mHandler = new PlayingBarHandler(new WeakReference<>(this));
}
public void onPause() {
mHandler.removeMessages(MSG_UPDATE_QUEUE);
mHandler.removeMessages(MSG_UPDATE_SEEK);
mHandler.removeMessages(MSG_UPDATE_FAB);
PlaybackProxy.removeCallback(mPlaybackCallback);
ProviderAggregator.getDefault().removeUpdateCallback(mProviderCallback);
mCallbackRegistered = false;
getContext().getSharedPreferences(SettingsKeys.PREF_SETTINGS, 0)
.unregisterOnSharedPreferenceChangeListener(this);
}
public void onResume() {
ProviderAggregator.getDefault().addUpdateCallback(mProviderCallback);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
ensurePlaybackCallback();
}
}, 500);
getContext().getSharedPreferences(SettingsKeys.PREF_SETTINGS, 0)
.registerOnSharedPreferenceChangeListener(this);
}
private void ensurePlaybackCallback() {
if (!mCallbackRegistered) {
mCallbackRegistered = true;
PlaybackProxy.addCallback(mPlaybackCallback);
}
// We delay check if we have a queue and/or are playing to leave time to the
// playback service to get up
if (mTracksLayout != null) {
mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE);
mHandler.sendEmptyMessage(MSG_UPDATE_FAB);
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SEEK, SEEK_BAR_UPDATE_DELAY);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Set FAB info
mPlayFab = (FabButton) findViewById(R.id.fabPlayBarButton);
mPlayFabDrawable = new PlayPauseDrawable(getResources(), 0.80f);
mPlayFabDrawable.setColor(getResources().getColor(R.color.white));
mPlayFabDrawable.setShape(PlayPauseDrawable.SHAPE_PLAY);
mPlayFabDrawable.setYOffset(0);
mPlayFab.setIconDrawable(mPlayFabDrawable);
mPlayFab.setVisibility(View.GONE);
mPlayFab.setOnTouchListener(new OnTouchListener() {
private boolean mIsDragging = false;
private float mStartY;
private float mSeekValue;
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
boolean result = false;
boolean isPlaying = (PlaybackProxy.getState() == PlaybackService.STATE_PLAYING);
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN && isPlaying) {
mIsDragging = true;
mStartY = motionEvent.getY();
} else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE && mIsDragging && isPlaying) {
float deltaStart = motionEvent.getY() - mStartY;
if (Math.abs(deltaStart) > view.getMeasuredHeight() / 2.0f) {
// We're dragging the play button, start seek mode
mPlayInSeekMode = true;
mSeekValue = Math.max(0,
Math.min(-deltaStart * 500.0f, mPlayFab.getMaxProgress()));
mPlayFab.showProgress(true);
mPlayFab.setProgress(mSeekValue);
}
result = true;
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
if (mPlayInSeekMode) {
mPlayInSeekMode = false;
PlaybackProxy.seek((long) mSeekValue);
// If we consume the up action, we cannot "unselect"/"unfocus" the FAB
// and it remains in a "selected" state after we lift our finger. We
// work around this issue by skipping the FAB action once
mSkipFabAction = true;
}
}
return result;
}
});
mPlayFab.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (mSkipFabAction) {
mSkipFabAction = false;
} else {
if (mIsPlaying) {
PlaybackProxy.pause();
} else {
PlaybackProxy.play();
}
}
}
});
// Load playing queue
mTracksLayout = (LinearLayout) findViewById(R.id.playBarTracksLayout);
onResume();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
onPause();
}
@Override
public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
mGestureDetector.onTouchEvent(ev);
if (ev.getAction() == MotionEvent.ACTION_UP) {
mGestureListener.onTouchUp(ev);
}
return super.dispatchTouchEvent(ev);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (SettingsKeys.KEY_PLAYBAR_HIDDEN.equals(key)) {
ensurePlaybackCallback();
updatePlayingQueue();
}
}
public void updateSeekBar() {
int state = PlaybackProxy.getState();
if (state == PlaybackService.STATE_PLAYING
|| state == PlaybackService.STATE_PAUSING
|| state == PlaybackService.STATE_PAUSED) {
if (!mPlayInSeekMode) {
mPlayFab.showProgress(true);
mPlayFab.setProgress(PlaybackProxy.getCurrentTrackPosition());
}
// Restart ourselves
mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SEEK, SEEK_BAR_UPDATE_DELAY);
} else if (state == PlaybackService.STATE_BUFFERING) {
mPlayFab.setProgress(0);
mPlayFab.setIndeterminate(true);
mPlayFab.showProgress(true);
} else {
mPlayFab.showProgress(false);
}
}
public void updatePlayingQueue() {
List<Song> queue;
int currentIndex;
int playbackState = PlaybackProxy.getState();
boolean isPlaying = ((playbackState == PlaybackService.STATE_BUFFERING)
|| (playbackState == PlaybackService.STATE_PLAYING));
boolean hidden = getContext().getSharedPreferences(SettingsKeys.PREF_SETTINGS, 0)
.getBoolean(SettingsKeys.KEY_PLAYBAR_HIDDEN, false) && !isPlaying;
// Do a copy
queue = new ArrayList<>(PlaybackProxy.getCurrentPlaybackQueue());
currentIndex = PlaybackProxy.getCurrentTrackIndex();
if (queue.size() > 0 && !hidden) {
mLastQueue = new ArrayList<>(queue);
mTracksLayout.removeAllViews();
mTracksLayout.setVisibility(View.VISIBLE);
// Inflate views and make the list out of the first 4 items (or less)
int shownCount = 0;
final LayoutInflater inflater =
(LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final ProviderAggregator aggregator = ProviderAggregator.getDefault();
for (int i = 0; i < currentIndex; ++i) {
if (queue.size() > 0) {
queue.remove(0);
}
if (mLastQueue.size() > 0) {
mLastQueue.remove(0);
}
}
final int removedCount = currentIndex;
for (final Song song : queue) {
if (shownCount == MAX_PEEK_QUEUE_SIZE) {
break;
}
final int itemIndex = shownCount;
View itemRoot = inflater.inflate(R.layout.item_playbar, mTracksLayout, false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
itemRoot.setTransitionName("playbackqueue:preview:" + shownCount);
}
mTracksLayout.addView(itemRoot);
TextView tvArtist = (TextView) itemRoot.findViewById(R.id.tvArtist);
TextView tvTitle = (TextView) itemRoot.findViewById(R.id.tvTitle);
AlbumArtImageView ivAlbumArt = (AlbumArtImageView) itemRoot.findViewById(R.id.ivAlbumArt);
ivAlbumArt.setCrossfade(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tvArtist.setTransitionName("playbackqueue:preview:" + shownCount + ":artist");
tvTitle.setTransitionName("playbackqueue:preview:" + shownCount + ":title");
ivAlbumArt.setTransitionName("playbackqueue:preview:" + shownCount + ":art");
}
if (song != null && song.isLoaded() && song.getArtist() != null) {
Artist artist = aggregator.retrieveArtist(song.getArtist(), song.getProvider());
if (artist != null) {
tvArtist.setText(artist.getName());
} else {
tvArtist.setText(getContext().getString(R.string.loading));
}
} else if (song != null && song.isLoaded()) {
tvArtist.setText(null);
} else {
tvArtist.setText(R.string.loading);
}
if (song != null) {
tvTitle.setText(song.getTitle());
ivAlbumArt.loadArtForSong(song);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ivAlbumArt.setTransitionName(song.getRef() + shownCount);
}
}
// Set root view click listener
itemRoot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Play that song now
if (song != null && song.equals(PlaybackProxy.getCurrentTrack())) {
// We're already playing that song, play it again
PlaybackProxy.seek(0);
} else {
PlaybackProxy.playAtIndex(itemIndex + removedCount);
}
}
});
// Set album art click listener
ivAlbumArt.setOnClickListener(new View.OnClickListener() {
Palette mPalette;
@Override
public void onClick(View view) {
if (song == null || song.getAlbum() == null) {
Toast.makeText(view.getContext(),
R.string.toast_song_no_album, Toast.LENGTH_SHORT).show();
return;
}
Bitmap hero = ((MaterialTransitionDrawable) ((ImageView) view).getDrawable()).getFinalDrawable().getBitmap();
if (mPalette == null) {
mPalette = Palette.from(hero).generate();
}
Palette.Swatch darkVibrantColor = mPalette.getDarkVibrantSwatch();
Palette.Swatch darkMutedColor = mPalette.getDarkMutedSwatch();
int color;
if (darkVibrantColor != null) {
color = darkVibrantColor.getRgb();
} else if (darkMutedColor != null) {
color = darkMutedColor.getRgb();
} else {
color = getResources().getColor(R.color.default_album_art_background);
}
Intent intent = AlbumActivity.craftIntent(getContext(), hero, song.getAlbum(),
song.getProvider(), color);
if (Utils.hasLollipop()) {
ActivityOptions opt = ActivityOptions.makeSceneTransitionAnimation((Activity) getContext(),
view, "itemImage");
getContext().startActivity(intent, opt.toBundle());
} else {
getContext().startActivity(intent);
}
}
});
if (shownCount == 0) {
itemRoot.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (mWrapped) {
setWrapped(false);
}
}
});
}
shownCount++;
}
// Add the "View full queue" item entry, using the album art as "wrap" button
final View itemRoot = inflater.inflate(R.layout.item_playbar_action, mTracksLayout, false);
mTracksLayout.addView(itemRoot);
TextView tvTitle = (TextView) itemRoot.findViewById(R.id.tvTitle);
tvTitle.setText(getContext().getString(R.string.view_full_queue));
itemRoot.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(getContext(), PlaybackQueueActivity.class);
getContext().startActivity(intent);
}
});
// Update wrap status
setWrapped(mWrapped, false);
setFabVisible(true);
} else {
mWrapped = true;
mTracksLayout.setVisibility(View.GONE);
setFabVisible(false);
}
}
public void updatePlayFab() {
int state = PlaybackProxy.getState();
if (mPlayFab != null && mPlayFabDrawable != null) {
switch (state) {
case PlaybackService.STATE_PAUSED:
case PlaybackService.STATE_STOPPED:
mPlayFabDrawable.setShape(PlayPauseDrawable.SHAPE_PLAY);
mPlayFab.showProgress(false);
mIsPlaying = false;
break;
case PlaybackService.STATE_BUFFERING:
case PlaybackService.STATE_PAUSING:
mPlayFabDrawable.setShape(PlayPauseDrawable.SHAPE_PAUSE);
mPlayFab.showProgress(true);
mPlayFab.setIndeterminate(true);
mIsPlaying = true;
break;
case PlaybackService.STATE_PLAYING:
mPlayFabDrawable.setShape(PlayPauseDrawable.SHAPE_PAUSE);
mPlayFab.setIndeterminate(false);
mIsPlaying = true;
break;
}
}
}
public void setFabVisible(boolean visible) {
if (visible && mPlayFab.getVisibility() == View.VISIBLE
|| !visible && mPlayFab.getVisibility() == View.GONE) {
return;
}
int startRadius;
int finalRadius;
// get the center for the clipping circle
final int cx = mPlayFab.getMeasuredWidth() / 2;
final int cy = mPlayFab.getMeasuredHeight() / 2;
if (visible) {
mPlayFab.setVisibility(View.VISIBLE);
startRadius = 0;
finalRadius = mPlayFab.getWidth();
} else {
startRadius = mPlayFab.getWidth();
finalRadius = 0;
}
// create and start the animator for this view (the start radius is zero)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mPlayFab.isAttachedToWindow()) {
Animator anim =
ViewAnimationUtils.createCircularReveal(mPlayFab, cx, cy, startRadius, finalRadius);
anim.setInterpolator(new DecelerateInterpolator());
if (!visible) {
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
mPlayFab.setVisibility(View.GONE);
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
anim.start();
}
} else {
// TODO: Kitkat animation
}
}
public void setWrapped(boolean wrapped, boolean animation) {
if (mIsHiding) {
return;
}
if (wrapped && mLastQueue != null) {
final int itemHeight = getResources().getDimensionPixelSize(R.dimen.playing_bar_height);
final int translationY = itemHeight * Math.min(mLastQueue.size(), MAX_PEEK_QUEUE_SIZE);
if (animation) {
animate().translationY(translationY)
.setDuration(mAnimationDuration)
.start();
} else {
setTranslationY(translationY);
}
} else {
if (animation) {
animate().translationY(0).setDuration(mAnimationDuration).start();
} else {
setTranslationY(0);
}
}
mWrapped = wrapped;
}
public void setWrapped(boolean wrapped) {
setWrapped(wrapped, true);
}
public boolean isWrapped() {
return mWrapped;
}
public boolean isVisible() {
if (mTracksLayout != null) {
return mTracksLayout.getVisibility() == View.VISIBLE;
} else {
return false;
}
}
private class BarGestureListener extends GestureDetector.SimpleOnGestureListener {
private int mTotalDistanceX = 0;
private int mTotalDistanceY = 0;
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
mTotalDistanceX += Math.abs(distanceX);
mTotalDistanceY += Math.abs(distanceY);
if (mTotalDistanceX > mTotalDistanceY && mTotalDistanceX > 20) {
boolean isPlaying = PlaybackProxy.getState() == PlaybackService.STATE_PLAYING;
if (!isPlaying) {
setTranslationX(getTranslationX() + (e2.getX() - e1.getX()));
float alpha = 1.0f - Math.abs(getTranslationX() / getMeasuredWidth());
setAlpha(alpha);
}
} else {
setTranslationX(0);
setAlpha(1);
}
return true;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float vX, float vY) {
final float avX = Math.abs(vX);
final float avY = Math.abs(vY);
if (avX > avY) {
if (avX > 400) {
swipeAway();
return true;
}
} else if (avY > avX) {
if (avY > 500) {
setWrapped(vY > 0);
return true;
}
}
return false;
}
private void swipeAway() {
setWrapped(true);
mIsHiding = true;
PlaybackProxy.stop();
getContext().getSharedPreferences(SettingsKeys.PREF_SETTINGS, 0)
.edit().putBoolean(SettingsKeys.KEY_PLAYBAR_HIDDEN, true).apply();
mCallbackRegistered = false;
animate().translationX(getTranslationX() > 0 ? getMeasuredWidth() : -getMeasuredWidth())
.setDuration(200).setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
mWrapped = true;
mTracksLayout.setVisibility(View.GONE);
setFabVisible(false);
setTranslationX(0);
setAlpha(1.0f);
animate().setListener(null);
mIsHiding = false;
}
@Override
public void onAnimationCancel(Animator animation) {
animate().setListener(null);
setTranslationX(0);
setAlpha(1.0f);
mIsHiding = false;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}).start();
}
public void onTouchUp(MotionEvent ev) {
mTotalDistanceX = 0;
mTotalDistanceY = 0;
final int halfWidth = getMeasuredWidth() / 2;
if (getTranslationX() > halfWidth || getTranslationX() < -halfWidth) {
swipeAway();
} else if (getTranslationX() != 0) {
mIsHiding = true;
animate().translationX(0).alpha(1).setDuration(200).setListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
animate().setListener(null);
mIsHiding = false;
}
@Override
public void onAnimationCancel(Animator animation) {
animate().setListener(null);
mIsHiding = false;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}).start();
}
}
}
}