/** * Wire * Copyright (C) 2016 Wire Swiss GmbH * * 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.waz.zclient.ui.views; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import com.waz.api.Message; import com.waz.zclient.core.api.scala.ModelObserver; import com.waz.zclient.ui.R; import org.threeten.bp.Instant; import org.threeten.bp.temporal.ChronoUnit; public class EphemeralDotAnimationView extends View { private static final int NUMBER_OF_DOTS = 5; private static final int DEFAULT_PRIMARY_COLOR = Color.GRAY; private static final int DEFAULT_SECONDARY_COLOR = Color.LTGRAY; private int dotHeight; private int dotPadding; private int primaryColor; private int secondaryColor; private int selectedCount; private Paint paint; private ValueAnimator valueAnimator; private ModelObserver<Message> messageModelObserver = new ModelObserver<Message>() { @Override public void updated(Message message) { if (message.isEphemeral()) { if (message.getExpirationTime() == Instant.MAX) { setVisibility(View.VISIBLE); showAllDots(); } else { long diff = ChronoUnit.MILLIS.between(Instant.now(), message.getExpirationTime()); if (diff > 0) { startAnimation(message.getEphemeralExpiration().milliseconds, diff); } else { setVisibility(View.GONE); } } } else { setVisibility(View.GONE); } } }; public EphemeralDotAnimationView(Context context) { this(context, null); } public EphemeralDotAnimationView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public EphemeralDotAnimationView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(attrs); } private void init(AttributeSet attrs) { primaryColor = DEFAULT_PRIMARY_COLOR; secondaryColor = DEFAULT_SECONDARY_COLOR; dotPadding = 0; if (attrs != null) { TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.EphemeralDotAnimationView); if (ta != null) { primaryColor = ta.getColor(R.styleable.EphemeralDotAnimationView_primaryColor, primaryColor); secondaryColor = ta.getColor(R.styleable.EphemeralDotAnimationView_secondaryColor, secondaryColor); dotPadding = ta.getDimensionPixelSize(R.styleable.EphemeralDotAnimationView_dotPadding, dotPadding); ta.recycle(); } } paint = new Paint(Paint.ANTI_ALIAS_FLAG); setVisibility(GONE); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (canvas == null || canvas.getHeight() == 0) { return; } int x = canvas.getWidth() / 2; int initialY = getPaddingTop() + dotHeight / 2; float radius = (float) dotHeight / 2; for (int i = 0; i < NUMBER_OF_DOTS; i++) { paint.setColor(i + selectedCount >= NUMBER_OF_DOTS ? primaryColor : secondaryColor); int y = dotHeight * i + initialY + dotPadding * i; canvas.drawCircle(x, y, radius, paint); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); int totalDotPadding = dotPadding * (NUMBER_OF_DOTS - 1); dotHeight = (height - totalDotPadding) / NUMBER_OF_DOTS; invalidate(); } public void setDotPadding(int dotPadding) { this.dotPadding = dotPadding; } public void setPrimaryColor(int primaryColor) { this.primaryColor = primaryColor; } public void setSecondaryColor(int secondaryColor) { this.secondaryColor = secondaryColor; } private void cancelAnimation() { if (valueAnimator != null) { valueAnimator.cancel(); valueAnimator = null; } } private void showAllDots() { selectedCount = NUMBER_OF_DOTS; invalidate(); } private void startAnimation(long totalMilliSeconds, long remainingMilliSeconds) { if (valueAnimator != null) { valueAnimator.cancel(); } valueAnimator = ObjectAnimator.ofInt(NUMBER_OF_DOTS, 0); valueAnimator.setDuration(totalMilliSeconds); valueAnimator.setCurrentPlayTime(totalMilliSeconds - remainingMilliSeconds); valueAnimator.setInterpolator(null); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { int updatedValue = (int) animation.getAnimatedValue(); if (updatedValue != selectedCount) { selectedCount = updatedValue; invalidate(); } } }); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { setVisibility(GONE); } }); setVisibility(View.VISIBLE); valueAnimator.start(); } public void setMessage(Message message) { cancelAnimation(); if (message == null) { messageModelObserver.clear(); setVisibility(GONE); } else { messageModelObserver.setAndUpdate(message); } } }