mirror of https://github.com/NekoX-Dev/NekoX.git
1622 lines
64 KiB
Java
1622 lines
64 KiB
Java
package org.telegram.ui.Components;
|
|
|
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.AnimatorListenerAdapter;
|
|
import android.animation.ObjectAnimator;
|
|
import android.annotation.SuppressLint;
|
|
import android.app.Dialog;
|
|
import android.content.Context;
|
|
import android.content.res.Configuration;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Paint;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.PorterDuffColorFilter;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.graphics.Typeface;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Build;
|
|
import android.text.StaticLayout;
|
|
import android.text.TextPaint;
|
|
import android.text.TextUtils;
|
|
import android.text.method.LinkMovementMethod;
|
|
import android.util.Property;
|
|
import android.util.TypedValue;
|
|
import android.view.GestureDetector;
|
|
import android.view.Gravity;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.Window;
|
|
import android.view.WindowManager;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
|
|
import androidx.annotation.CallSuper;
|
|
import androidx.annotation.IntDef;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.core.util.Consumer;
|
|
import androidx.core.view.ViewCompat;
|
|
import androidx.dynamicanimation.animation.DynamicAnimation;
|
|
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
|
import androidx.dynamicanimation.animation.FloatValueHolder;
|
|
import androidx.dynamicanimation.animation.SpringAnimation;
|
|
import androidx.dynamicanimation.animation.SpringForce;
|
|
|
|
import org.telegram.messenger.AndroidUtilities;
|
|
import org.telegram.messenger.LocaleController;
|
|
import org.telegram.messenger.MessagesController;
|
|
import org.telegram.messenger.R;
|
|
import org.telegram.tgnet.TLRPC;
|
|
import org.telegram.ui.ActionBar.BaseFragment;
|
|
import org.telegram.ui.ActionBar.Theme;
|
|
import org.telegram.ui.ChatActivity;
|
|
import org.telegram.ui.DialogsActivity;
|
|
|
|
import java.lang.annotation.Retention;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
|
|
public class Bulletin {
|
|
|
|
public static final int DURATION_SHORT = 1500;
|
|
public static final int DURATION_LONG = 2750;
|
|
public static final int DURATION_PROLONG = 5000;
|
|
|
|
public static final int TYPE_STICKER = 0;
|
|
public static final int TYPE_ERROR = 1;
|
|
public static final int TYPE_BIO_CHANGED = 2;
|
|
public static final int TYPE_NAME_CHANGED = 3;
|
|
public static final int TYPE_ERROR_SUBTITLE = 4;
|
|
public static final int TYPE_APP_ICON = 5;
|
|
|
|
public int tag;
|
|
public int hash;
|
|
private View.OnLayoutChangeListener containerLayoutListener;
|
|
private SpringAnimation bottomOffsetSpring;
|
|
|
|
public static Bulletin make(@NonNull FrameLayout containerLayout, @NonNull Layout contentLayout, int duration) {
|
|
return new Bulletin(null, containerLayout, contentLayout, duration);
|
|
}
|
|
|
|
@SuppressLint("RtlHardcoded")
|
|
public static Bulletin make(@NonNull BaseFragment fragment, @NonNull Layout contentLayout, int duration) {
|
|
if (fragment instanceof ChatActivity) {
|
|
contentLayout.setWideScreenParams(ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.RIGHT);
|
|
} else if (fragment instanceof DialogsActivity) {
|
|
contentLayout.setWideScreenParams(ViewGroup.LayoutParams.MATCH_PARENT, Gravity.NO_GRAVITY);
|
|
}
|
|
return new Bulletin(fragment, fragment.getLayoutContainer(), contentLayout, duration);
|
|
}
|
|
|
|
public static Bulletin find(@NonNull FrameLayout containerLayout) {
|
|
for (int i = 0, size = containerLayout.getChildCount(); i < size; i++) {
|
|
final View view = containerLayout.getChildAt(i);
|
|
if (view instanceof Layout) {
|
|
return ((Layout) view).bulletin;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static void hide(@NonNull FrameLayout containerLayout) {
|
|
hide(containerLayout, true);
|
|
}
|
|
|
|
public static void hide(@NonNull FrameLayout containerLayout, boolean animated) {
|
|
final Bulletin bulletin = find(containerLayout);
|
|
if (bulletin != null) {
|
|
bulletin.hide(animated && isTransitionsEnabled(), 0);
|
|
}
|
|
}
|
|
|
|
private static final HashMap<FrameLayout, Delegate> delegates = new HashMap<>();
|
|
private static final HashMap<BaseFragment, Delegate> fragmentDelegates = new HashMap<>();
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
|
private static Bulletin visibleBulletin;
|
|
|
|
private final Layout layout;
|
|
private final ParentLayout parentLayout;
|
|
private final BaseFragment containerFragment;
|
|
private final FrameLayout containerLayout;
|
|
private final Runnable hideRunnable = this::hide;
|
|
private int duration;
|
|
|
|
private boolean showing;
|
|
private boolean canHide;
|
|
public int currentBottomOffset;
|
|
private Delegate currentDelegate;
|
|
private Layout.Transition layoutTransition;
|
|
|
|
private Bulletin() {
|
|
layout = null;
|
|
parentLayout = null;
|
|
containerFragment = null;
|
|
containerLayout = null;
|
|
}
|
|
|
|
private Bulletin(BaseFragment fragment, @NonNull FrameLayout containerLayout, @NonNull Layout layout, int duration) {
|
|
this.layout = layout;
|
|
this.parentLayout = new ParentLayout(layout) {
|
|
@Override
|
|
protected void onPressedStateChanged(boolean pressed) {
|
|
setCanHide(!pressed);
|
|
if (containerLayout.getParent() != null) {
|
|
containerLayout.getParent().requestDisallowInterceptTouchEvent(pressed);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onHide() {
|
|
hide();
|
|
}
|
|
};
|
|
this.containerFragment = fragment;
|
|
this.containerLayout = containerLayout;
|
|
this.duration = duration;
|
|
}
|
|
|
|
public static Bulletin getVisibleBulletin() {
|
|
return visibleBulletin;
|
|
}
|
|
|
|
public static void hideVisible() {
|
|
if (visibleBulletin != null) {
|
|
visibleBulletin.hide();
|
|
}
|
|
}
|
|
|
|
public void setDuration(int duration) {
|
|
this.duration = duration;
|
|
}
|
|
|
|
public Bulletin show() {
|
|
return show(false);
|
|
}
|
|
|
|
public Bulletin show(boolean top) {
|
|
if (!showing && containerLayout != null) {
|
|
showing = true;
|
|
layout.setTop(top);
|
|
|
|
CharSequence text = layout.getAccessibilityText();
|
|
if (text != null) {
|
|
AndroidUtilities.makeAccessibilityAnnouncement(text);
|
|
}
|
|
|
|
if (layout.getParent() != parentLayout) {
|
|
throw new IllegalStateException("Layout has incorrect parent");
|
|
}
|
|
|
|
if (visibleBulletin != null) {
|
|
visibleBulletin.hide();
|
|
}
|
|
visibleBulletin = this;
|
|
layout.onAttach(this);
|
|
|
|
containerLayout.addOnLayoutChangeListener(containerLayoutListener = (v, left, top1, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
|
if (!top) {
|
|
int newOffset = currentDelegate != null ? currentDelegate.getBottomOffset(tag) : 0;
|
|
if (currentBottomOffset != newOffset) {
|
|
if (bottomOffsetSpring == null || !bottomOffsetSpring.isRunning()) {
|
|
bottomOffsetSpring = new SpringAnimation(new FloatValueHolder(currentBottomOffset))
|
|
.setSpring(new SpringForce()
|
|
.setFinalPosition(newOffset)
|
|
.setStiffness(900f)
|
|
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
|
|
bottomOffsetSpring.addUpdateListener((animation, value, velocity) -> {
|
|
currentBottomOffset = (int) value;
|
|
updatePosition();
|
|
});
|
|
bottomOffsetSpring.addEndListener((animation, canceled, value, velocity) -> {
|
|
if (bottomOffsetSpring == animation) {
|
|
bottomOffsetSpring = null;
|
|
}
|
|
});
|
|
} else {
|
|
bottomOffsetSpring.getSpring().setFinalPosition(newOffset);
|
|
}
|
|
bottomOffsetSpring.start();
|
|
}
|
|
}
|
|
});
|
|
|
|
layout.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
|
|
@Override
|
|
public void onLayoutChange(View v, int left, int t, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
|
layout.removeOnLayoutChangeListener(this);
|
|
if (showing) {
|
|
layout.onShow();
|
|
currentDelegate = findDelegate(containerFragment, containerLayout);
|
|
if (bottomOffsetSpring == null || !bottomOffsetSpring.isRunning()) {
|
|
currentBottomOffset = currentDelegate != null ? currentDelegate.getBottomOffset(tag) : 0;
|
|
}
|
|
if (currentDelegate != null) {
|
|
currentDelegate.onShow(Bulletin.this);
|
|
}
|
|
if (isTransitionsEnabled()) {
|
|
ensureLayoutTransitionCreated();
|
|
layout.transitionRunningEnter = true;
|
|
layout.delegate = currentDelegate;
|
|
layout.invalidate();
|
|
layoutTransition.animateEnter(layout, layout::onEnterTransitionStart, () -> {
|
|
layout.transitionRunningEnter = false;
|
|
layout.onEnterTransitionEnd();
|
|
setCanHide(true);
|
|
}, offset -> {
|
|
if (currentDelegate != null && !top) {
|
|
currentDelegate.onBottomOffsetChange(layout.getHeight() - offset);
|
|
}
|
|
}, currentBottomOffset);
|
|
} else {
|
|
if (currentDelegate != null && !top) {
|
|
currentDelegate.onBottomOffsetChange(layout.getHeight() - currentBottomOffset);
|
|
}
|
|
updatePosition();
|
|
layout.onEnterTransitionStart();
|
|
layout.onEnterTransitionEnd();
|
|
setCanHide(true);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
layout.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
|
@Override
|
|
public void onViewAttachedToWindow(View v) {
|
|
}
|
|
|
|
@Override
|
|
public void onViewDetachedFromWindow(View v) {
|
|
layout.removeOnAttachStateChangeListener(this);
|
|
hide(false, 0);
|
|
}
|
|
});
|
|
|
|
containerLayout.addView(parentLayout);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
private void setCanHide(boolean canHide) {
|
|
if (this.canHide != canHide && layout != null) {
|
|
this.canHide = canHide;
|
|
if (canHide) {
|
|
layout.postDelayed(hideRunnable, duration);
|
|
} else {
|
|
layout.removeCallbacks(hideRunnable);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ensureLayoutTransitionCreated() {
|
|
if (layout != null && layoutTransition == null) {
|
|
layoutTransition = layout.createTransition();
|
|
}
|
|
}
|
|
|
|
public void hide() {
|
|
hide(isTransitionsEnabled(), 0);
|
|
}
|
|
|
|
public void hide(long duration) {
|
|
hide(isTransitionsEnabled(), duration);
|
|
}
|
|
|
|
public void hide(boolean animated, long duration) {
|
|
if (layout == null) {
|
|
return;
|
|
}
|
|
if (showing) {
|
|
showing = false;
|
|
|
|
if (visibleBulletin == this) {
|
|
visibleBulletin = null;
|
|
}
|
|
|
|
int bottomOffset = currentBottomOffset;
|
|
currentBottomOffset = 0;
|
|
|
|
if (ViewCompat.isLaidOut(layout)) {
|
|
layout.removeCallbacks(hideRunnable);
|
|
if (animated) {
|
|
layout.transitionRunningExit = true;
|
|
layout.delegate = currentDelegate;
|
|
layout.invalidate();
|
|
if (duration >= 0) {
|
|
Layout.DefaultTransition transition = new Layout.DefaultTransition();
|
|
transition.duration = duration;
|
|
layoutTransition = transition;
|
|
} else {
|
|
ensureLayoutTransitionCreated();
|
|
}
|
|
layoutTransition.animateExit(layout, layout::onExitTransitionStart, () -> {
|
|
if (currentDelegate != null && !layout.top) {
|
|
currentDelegate.onBottomOffsetChange(0);
|
|
currentDelegate.onHide(this);
|
|
}
|
|
layout.transitionRunningExit = false;
|
|
layout.onExitTransitionEnd();
|
|
layout.onHide();
|
|
containerLayout.removeView(parentLayout);
|
|
containerLayout.removeOnLayoutChangeListener(containerLayoutListener);
|
|
layout.onDetach();
|
|
}, offset -> {
|
|
if (currentDelegate != null && !layout.top) {
|
|
currentDelegate.onBottomOffsetChange(layout.getHeight() - offset);
|
|
}
|
|
}, bottomOffset);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (currentDelegate != null && !layout.top) {
|
|
currentDelegate.onBottomOffsetChange(0);
|
|
currentDelegate.onHide(this);
|
|
}
|
|
layout.onExitTransitionStart();
|
|
layout.onExitTransitionEnd();
|
|
layout.onHide();
|
|
if (containerLayout != null) {
|
|
AndroidUtilities.runOnUIThread(() -> {
|
|
containerLayout.removeView(parentLayout);
|
|
containerLayout.removeOnLayoutChangeListener(containerLayoutListener);
|
|
});
|
|
}
|
|
layout.onDetach();
|
|
}
|
|
}
|
|
|
|
public boolean isShowing() {
|
|
return showing;
|
|
}
|
|
|
|
public Layout getLayout() {
|
|
return layout;
|
|
}
|
|
|
|
private static boolean isTransitionsEnabled() {
|
|
return MessagesController.getGlobalMainSettings().getBoolean("view_animations", true) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
|
|
}
|
|
|
|
public void updatePosition() {
|
|
if (layout != null) {
|
|
layout.updatePosition();
|
|
}
|
|
}
|
|
|
|
@Retention(SOURCE)
|
|
@IntDef(value = {ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT})
|
|
private @interface WidthDef {
|
|
}
|
|
|
|
@Retention(SOURCE)
|
|
@SuppressLint("RtlHardcoded")
|
|
@IntDef(value = {Gravity.LEFT, Gravity.RIGHT, Gravity.CENTER_HORIZONTAL, Gravity.NO_GRAVITY})
|
|
private @interface GravityDef {
|
|
}
|
|
|
|
private static abstract class ParentLayout extends FrameLayout {
|
|
|
|
private final Layout layout;
|
|
private final Rect rect = new Rect();
|
|
private final GestureDetector gestureDetector;
|
|
|
|
private boolean pressed;
|
|
private float translationX;
|
|
private boolean hideAnimationRunning;
|
|
private boolean needLeftAlphaAnimation;
|
|
private boolean needRightAlphaAnimation;
|
|
|
|
public ParentLayout(Layout layout) {
|
|
super(layout.getContext());
|
|
this.layout = layout;
|
|
gestureDetector = new GestureDetector(layout.getContext(), new GestureDetector.SimpleOnGestureListener() {
|
|
|
|
@Override
|
|
public boolean onDown(MotionEvent e) {
|
|
if (!hideAnimationRunning) {
|
|
needLeftAlphaAnimation = layout.isNeedSwipeAlphaAnimation(true);
|
|
needRightAlphaAnimation = layout.isNeedSwipeAlphaAnimation(false);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
|
layout.setTranslationX(translationX -= distanceX);
|
|
if (translationX == 0 || (translationX < 0f && needLeftAlphaAnimation) || (translationX > 0f && needRightAlphaAnimation)) {
|
|
layout.setAlpha(1f - Math.abs(translationX) / layout.getWidth());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
|
if (Math.abs(velocityX) > 2000f) {
|
|
final boolean needAlphaAnimation = (velocityX < 0f && needLeftAlphaAnimation) || (velocityX > 0f && needRightAlphaAnimation);
|
|
|
|
final SpringAnimation springAnimation = new SpringAnimation(layout, DynamicAnimation.TRANSLATION_X, Math.signum(velocityX) * layout.getWidth() * 2f);
|
|
if (!needAlphaAnimation) {
|
|
springAnimation.addEndListener((animation, canceled, value, velocity) -> onHide());
|
|
springAnimation.addUpdateListener(((animation, value, velocity) -> {
|
|
if (Math.abs(value) > layout.getWidth()) {
|
|
animation.cancel();
|
|
}
|
|
}));
|
|
}
|
|
springAnimation.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
|
|
springAnimation.getSpring().setStiffness(100f);
|
|
springAnimation.setStartVelocity(velocityX);
|
|
springAnimation.start();
|
|
|
|
if (needAlphaAnimation) {
|
|
final SpringAnimation springAnimation2 = new SpringAnimation(layout, DynamicAnimation.ALPHA, 0f);
|
|
springAnimation2.addEndListener((animation, canceled, value, velocity) -> onHide());
|
|
springAnimation2.addUpdateListener(((animation, value, velocity) -> {
|
|
if (value <= 0f) {
|
|
animation.cancel();
|
|
}
|
|
}));
|
|
springAnimation.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
|
|
springAnimation.getSpring().setStiffness(10f);
|
|
springAnimation.setStartVelocity(velocityX);
|
|
springAnimation2.start();
|
|
}
|
|
|
|
hideAnimationRunning = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
gestureDetector.setIsLongpressEnabled(false);
|
|
addView(layout);
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
if (pressed || inLayoutHitRect(event.getX(), event.getY())) {
|
|
gestureDetector.onTouchEvent(event);
|
|
final int actionMasked = event.getActionMasked();
|
|
if (actionMasked == MotionEvent.ACTION_DOWN) {
|
|
if (!pressed && !hideAnimationRunning) {
|
|
layout.animate().cancel();
|
|
translationX = layout.getTranslationX();
|
|
onPressedStateChanged(pressed = true);
|
|
}
|
|
} else if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL) {
|
|
if (pressed) {
|
|
if (!hideAnimationRunning) {
|
|
if (Math.abs(translationX) > layout.getWidth() / 3f) {
|
|
final float tx = Math.signum(translationX) * layout.getWidth();
|
|
final boolean needAlphaAnimation = (translationX < 0f && needLeftAlphaAnimation) || (translationX > 0f && needRightAlphaAnimation);
|
|
layout.animate().translationX(tx).alpha(needAlphaAnimation ? 0f : 1f).setDuration(200).setInterpolator(AndroidUtilities.accelerateInterpolator).withEndAction(() -> {
|
|
if (layout.getTranslationX() == tx) {
|
|
onHide();
|
|
}
|
|
}).start();
|
|
} else {
|
|
layout.animate().translationX(0).alpha(1f).setDuration(200).start();
|
|
}
|
|
}
|
|
onPressedStateChanged(pressed = false);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean inLayoutHitRect(float x, float y) {
|
|
layout.getHitRect(rect);
|
|
return rect.contains((int) x, (int) y);
|
|
}
|
|
|
|
protected abstract void onPressedStateChanged(boolean pressed);
|
|
|
|
protected abstract void onHide();
|
|
}
|
|
|
|
//region Offset Providers
|
|
public static void addDelegate(@NonNull BaseFragment fragment, @NonNull Delegate delegate) {
|
|
fragmentDelegates.put(fragment, delegate);
|
|
}
|
|
|
|
public static void addDelegate(@NonNull FrameLayout containerLayout, @NonNull Delegate delegate) {
|
|
delegates.put(containerLayout, delegate);
|
|
}
|
|
|
|
private static Delegate findDelegate(BaseFragment probableFragment, FrameLayout probableContainer) {
|
|
Delegate delegate;
|
|
if ((delegate = fragmentDelegates.get(probableFragment)) != null) {
|
|
return delegate;
|
|
}
|
|
if ((delegate = delegates.get(probableContainer)) != null) {
|
|
return delegate;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static void removeDelegate(@NonNull BaseFragment fragment) {
|
|
fragmentDelegates.remove(fragment);
|
|
}
|
|
|
|
public static void removeDelegate(@NonNull FrameLayout containerLayout) {
|
|
delegates.remove(containerLayout);
|
|
}
|
|
|
|
public interface Delegate {
|
|
|
|
default int getBottomOffset(int tag) {
|
|
return 0;
|
|
}
|
|
|
|
default int getTopOffset(int tag) {
|
|
return 0;
|
|
}
|
|
|
|
default void onBottomOffsetChange(float offset) {
|
|
}
|
|
|
|
default void onShow(Bulletin bulletin) {
|
|
}
|
|
|
|
default void onHide(Bulletin bulletin) {
|
|
}
|
|
}
|
|
//endregion
|
|
|
|
//region Layouts
|
|
public abstract static class Layout extends FrameLayout {
|
|
|
|
private final List<Callback> callbacks = new ArrayList<>();
|
|
public boolean transitionRunningEnter;
|
|
public boolean transitionRunningExit;
|
|
Delegate delegate;
|
|
public float inOutOffset;
|
|
|
|
protected Bulletin bulletin;
|
|
Drawable background;
|
|
private boolean top;
|
|
|
|
public boolean isTransitionRunning() {
|
|
return transitionRunningEnter || transitionRunningExit;
|
|
}
|
|
|
|
@WidthDef
|
|
private int wideScreenWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
|
|
@GravityDef
|
|
private int wideScreenGravity = Gravity.CENTER_HORIZONTAL;
|
|
private final Theme.ResourcesProvider resourcesProvider;
|
|
|
|
public Layout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context);
|
|
this.resourcesProvider = resourcesProvider;
|
|
setMinimumHeight(AndroidUtilities.dp(48));
|
|
setBackground(getThemedColor(Theme.key_undo_background));
|
|
updateSize();
|
|
setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8));
|
|
setWillNotDraw(false);
|
|
}
|
|
|
|
protected void setBackground(int color) {
|
|
background = Theme.createRoundRectDrawable(AndroidUtilities.dp(10), color);
|
|
}
|
|
|
|
public final static FloatPropertyCompat<Layout> IN_OUT_OFFSET_Y = new FloatPropertyCompat<Layout>("offsetY") {
|
|
@Override
|
|
public float getValue(Layout object) {
|
|
return object.inOutOffset;
|
|
}
|
|
|
|
@Override
|
|
public void setValue(Layout object, float value) {
|
|
object.setInOutOffset(value);
|
|
}
|
|
};
|
|
|
|
public final static Property<Layout, Float> IN_OUT_OFFSET_Y2 = new AnimationProperties.FloatProperty<Layout>("offsetY") {
|
|
|
|
@Override
|
|
public Float get(Layout layout) {
|
|
return layout.inOutOffset;
|
|
}
|
|
|
|
@Override
|
|
public void setValue(Layout object, float value) {
|
|
object.setInOutOffset(value);
|
|
}
|
|
};
|
|
|
|
@Override
|
|
protected void onConfigurationChanged(Configuration newConfig) {
|
|
super.onConfigurationChanged(newConfig);
|
|
updateSize();
|
|
}
|
|
|
|
private void setTop(boolean top) {
|
|
this.top = top;
|
|
updateSize();
|
|
}
|
|
|
|
private void updateSize() {
|
|
final boolean isWideScreen = isWideScreen();
|
|
setLayoutParams(LayoutHelper.createFrame(isWideScreen ? wideScreenWidth : LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, isWideScreen ? (top ? Gravity.TOP : Gravity.BOTTOM) | wideScreenGravity : (top ? Gravity.TOP : Gravity.BOTTOM)));
|
|
}
|
|
|
|
private boolean isWideScreen() {
|
|
return AndroidUtilities.isTablet() || AndroidUtilities.displaySize.x >= AndroidUtilities.displaySize.y;
|
|
}
|
|
|
|
private void setWideScreenParams(@WidthDef int width, @GravityDef int gravity) {
|
|
boolean changed = false;
|
|
|
|
if (wideScreenWidth != width) {
|
|
wideScreenWidth = width;
|
|
changed = true;
|
|
}
|
|
|
|
if (wideScreenGravity != gravity) {
|
|
wideScreenGravity = gravity;
|
|
changed = true;
|
|
}
|
|
|
|
if (isWideScreen() && changed) {
|
|
updateSize();
|
|
}
|
|
}
|
|
|
|
@SuppressLint("RtlHardcoded")
|
|
private boolean isNeedSwipeAlphaAnimation(boolean swipeLeft) {
|
|
if (!isWideScreen() || wideScreenWidth == ViewGroup.LayoutParams.MATCH_PARENT) {
|
|
return false;
|
|
}
|
|
if (wideScreenGravity == Gravity.CENTER_HORIZONTAL) {
|
|
return true;
|
|
}
|
|
if (swipeLeft) {
|
|
return wideScreenGravity == Gravity.RIGHT;
|
|
} else {
|
|
return wideScreenGravity != Gravity.RIGHT;
|
|
}
|
|
}
|
|
|
|
protected CharSequence getAccessibilityText() {
|
|
return null;
|
|
}
|
|
|
|
public Bulletin getBulletin() {
|
|
return bulletin;
|
|
}
|
|
|
|
public boolean isAttachedToBulletin() {
|
|
return bulletin != null;
|
|
}
|
|
|
|
@CallSuper
|
|
protected void onAttach(@NonNull Bulletin bulletin) {
|
|
this.bulletin = bulletin;
|
|
for (int i = 0, size = callbacks.size(); i < size; i++) {
|
|
callbacks.get(i).onAttach(this, bulletin);
|
|
}
|
|
}
|
|
|
|
@CallSuper
|
|
protected void onDetach() {
|
|
this.bulletin = null;
|
|
for (int i = 0, size = callbacks.size(); i < size; i++) {
|
|
callbacks.get(i).onDetach(this);
|
|
}
|
|
}
|
|
|
|
@CallSuper
|
|
protected void onShow() {
|
|
for (int i = 0, size = callbacks.size(); i < size; i++) {
|
|
callbacks.get(i).onShow(this);
|
|
}
|
|
}
|
|
|
|
@CallSuper
|
|
protected void onHide() {
|
|
for (int i = 0, size = callbacks.size(); i < size; i++) {
|
|
callbacks.get(i).onHide(this);
|
|
}
|
|
}
|
|
|
|
@CallSuper
|
|
protected void onEnterTransitionStart() {
|
|
for (int i = 0, size = callbacks.size(); i < size; i++) {
|
|
callbacks.get(i).onEnterTransitionStart(this);
|
|
}
|
|
}
|
|
|
|
@CallSuper
|
|
protected void onEnterTransitionEnd() {
|
|
for (int i = 0, size = callbacks.size(); i < size; i++) {
|
|
callbacks.get(i).onEnterTransitionEnd(this);
|
|
}
|
|
}
|
|
|
|
@CallSuper
|
|
protected void onExitTransitionStart() {
|
|
for (int i = 0, size = callbacks.size(); i < size; i++) {
|
|
callbacks.get(i).onExitTransitionStart(this);
|
|
}
|
|
}
|
|
|
|
@CallSuper
|
|
protected void onExitTransitionEnd() {
|
|
for (int i = 0, size = callbacks.size(); i < size; i++) {
|
|
callbacks.get(i).onExitTransitionEnd(this);
|
|
}
|
|
}
|
|
|
|
//region Callbacks
|
|
public void addCallback(@NonNull Callback callback) {
|
|
callbacks.add(callback);
|
|
}
|
|
|
|
public void removeCallback(@NonNull Callback callback) {
|
|
callbacks.remove(callback);
|
|
}
|
|
|
|
public void updatePosition() {
|
|
float translation = 0;
|
|
if (delegate != null) {
|
|
if (top) {
|
|
translation -= delegate.getTopOffset(bulletin != null ? bulletin.tag : 0);
|
|
} else {
|
|
translation += getBottomOffset();
|
|
}
|
|
}
|
|
setTranslationY(-translation + inOutOffset * (top ? -1 : 1));
|
|
}
|
|
|
|
public float getBottomOffset() {
|
|
if (bulletin != null && bulletin.bottomOffsetSpring != null && bulletin.bottomOffsetSpring.isRunning()) {
|
|
return bulletin.currentBottomOffset;
|
|
}
|
|
return delegate.getBottomOffset(bulletin != null ? bulletin.tag : 0);
|
|
}
|
|
|
|
public interface Callback {
|
|
default void onAttach(@NonNull Layout layout, @NonNull Bulletin bulletin) {
|
|
}
|
|
|
|
default void onDetach(@NonNull Layout layout) {
|
|
}
|
|
|
|
default void onShow(@NonNull Layout layout) {
|
|
}
|
|
|
|
default void onHide(@NonNull Layout layout) {
|
|
}
|
|
|
|
default void onEnterTransitionStart(@NonNull Layout layout) {
|
|
}
|
|
|
|
default void onEnterTransitionEnd(@NonNull Layout layout) {
|
|
}
|
|
|
|
default void onExitTransitionStart(@NonNull Layout layout) {
|
|
}
|
|
|
|
default void onExitTransitionEnd(@NonNull Layout layout) {
|
|
}
|
|
}
|
|
//endregion
|
|
|
|
//region Transitions
|
|
@NonNull
|
|
public Transition createTransition() {
|
|
return new SpringTransition();
|
|
}
|
|
|
|
public interface Transition {
|
|
void animateEnter(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer<Float> onUpdate, int bottomOffset);
|
|
|
|
void animateExit(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer<Float> onUpdate, int bottomOffset);
|
|
}
|
|
|
|
public static class DefaultTransition implements Transition {
|
|
|
|
long duration = 255;
|
|
|
|
@Override
|
|
public void animateEnter(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer<Float> onUpdate, int bottomOffset) {
|
|
layout.setInOutOffset(layout.getMeasuredHeight());
|
|
if (onUpdate != null) {
|
|
onUpdate.accept(layout.getTranslationY());
|
|
}
|
|
final ObjectAnimator animator = ObjectAnimator.ofFloat(layout, IN_OUT_OFFSET_Y2, 0);
|
|
animator.setDuration(duration);
|
|
animator.setInterpolator(Easings.easeOutQuad);
|
|
if (startAction != null || endAction != null) {
|
|
animator.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationStart(Animator animation) {
|
|
if (startAction != null) {
|
|
startAction.run();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
if (endAction != null) {
|
|
endAction.run();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (onUpdate != null) {
|
|
animator.addUpdateListener(a -> onUpdate.accept(layout.getTranslationY()));
|
|
}
|
|
animator.start();
|
|
}
|
|
|
|
@Override
|
|
public void animateExit(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer<Float> onUpdate, int bottomOffset) {
|
|
final ObjectAnimator animator = ObjectAnimator.ofFloat(layout, IN_OUT_OFFSET_Y2, layout.getHeight());
|
|
animator.setDuration(175);
|
|
animator.setInterpolator(Easings.easeInQuad);
|
|
if (startAction != null || endAction != null) {
|
|
animator.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationStart(Animator animation) {
|
|
if (startAction != null) {
|
|
startAction.run();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
if (endAction != null) {
|
|
endAction.run();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (onUpdate != null) {
|
|
animator.addUpdateListener(a -> onUpdate.accept(layout.getTranslationY()));
|
|
}
|
|
animator.start();
|
|
}
|
|
}
|
|
|
|
public static class SpringTransition implements Transition {
|
|
|
|
private static final float DAMPING_RATIO = 0.8f;
|
|
private static final float STIFFNESS = 400f;
|
|
|
|
@Override
|
|
public void animateEnter(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer<Float> onUpdate, int bottomOffset) {
|
|
layout.setInOutOffset(layout.getMeasuredHeight());
|
|
if (onUpdate != null) {
|
|
onUpdate.accept(layout.getTranslationY());
|
|
}
|
|
final SpringAnimation springAnimation = new SpringAnimation(layout, IN_OUT_OFFSET_Y, 0);
|
|
springAnimation.getSpring().setDampingRatio(DAMPING_RATIO);
|
|
springAnimation.getSpring().setStiffness(STIFFNESS);
|
|
if (endAction != null) {
|
|
springAnimation.addEndListener((animation, canceled, value, velocity) -> {
|
|
layout.setInOutOffset(0);
|
|
if (!canceled) {
|
|
endAction.run();
|
|
}
|
|
});
|
|
}
|
|
if (onUpdate != null) {
|
|
springAnimation.addUpdateListener((animation, value, velocity) -> onUpdate.accept(layout.getTranslationY()));
|
|
}
|
|
springAnimation.start();
|
|
if (startAction != null) {
|
|
startAction.run();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void animateExit(@NonNull Layout layout, @Nullable Runnable startAction, @Nullable Runnable endAction, @Nullable Consumer<Float> onUpdate, int bottomOffset) {
|
|
final SpringAnimation springAnimation = new SpringAnimation(layout, IN_OUT_OFFSET_Y, layout.getHeight());
|
|
springAnimation.getSpring().setDampingRatio(DAMPING_RATIO);
|
|
springAnimation.getSpring().setStiffness(STIFFNESS);
|
|
if (endAction != null) {
|
|
springAnimation.addEndListener((animation, canceled, value, velocity) -> {
|
|
if (!canceled) {
|
|
endAction.run();
|
|
}
|
|
});
|
|
}
|
|
if (onUpdate != null) {
|
|
springAnimation.addUpdateListener((animation, value, velocity) -> onUpdate.accept(layout.getTranslationY()));
|
|
}
|
|
springAnimation.start();
|
|
if (startAction != null) {
|
|
startAction.run();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void setInOutOffset(float offset) {
|
|
inOutOffset = offset;
|
|
updatePosition();
|
|
}
|
|
|
|
@Override
|
|
protected void dispatchDraw(Canvas canvas) {
|
|
if (bulletin == null) {
|
|
return;
|
|
}
|
|
background.setBounds(AndroidUtilities.dp(8), AndroidUtilities.dp(8), getMeasuredWidth() - AndroidUtilities.dp(8), getMeasuredHeight() - AndroidUtilities.dp(8));
|
|
if (isTransitionRunning() && delegate != null) {
|
|
canvas.save();
|
|
canvas.clipRect(
|
|
0,
|
|
delegate.getTopOffset(bulletin.tag) - getY(),
|
|
getMeasuredWidth(),
|
|
((View) getParent()).getMeasuredHeight() - getBottomOffset() - getY()
|
|
);
|
|
background.draw(canvas);
|
|
super.dispatchDraw(canvas);
|
|
canvas.restore();
|
|
invalidate();
|
|
} else {
|
|
background.draw(canvas);
|
|
super.dispatchDraw(canvas);
|
|
}
|
|
}
|
|
|
|
protected int getThemedColor(String key) {
|
|
Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null;
|
|
return color != null ? color : Theme.getColor(key);
|
|
}
|
|
//endregion
|
|
}
|
|
|
|
@SuppressLint("ViewConstructor")
|
|
public static class ButtonLayout extends Layout {
|
|
|
|
private Button button;
|
|
public TimerView timerView;
|
|
|
|
private int childrenMeasuredWidth;
|
|
Theme.ResourcesProvider resourcesProvider;
|
|
|
|
public ButtonLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context, resourcesProvider);
|
|
this.resourcesProvider = resourcesProvider;
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
childrenMeasuredWidth = 0;
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
if (button != null && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
|
|
setMeasuredDimension(childrenMeasuredWidth + button.getMeasuredWidth(), getMeasuredHeight());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
|
|
if (button != null && child != button) {
|
|
widthUsed += button.getMeasuredWidth() - AndroidUtilities.dp(12);
|
|
}
|
|
super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
|
|
if (child != button) {
|
|
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
|
|
childrenMeasuredWidth = Math.max(childrenMeasuredWidth, lp.leftMargin + lp.rightMargin + child.getMeasuredWidth());
|
|
}
|
|
}
|
|
|
|
public Button getButton() {
|
|
return button;
|
|
}
|
|
|
|
public void setButton(Button button) {
|
|
if (this.button != null) {
|
|
removeCallback(this.button);
|
|
removeView(this.button);
|
|
}
|
|
this.button = button;
|
|
if (button != null) {
|
|
addCallback(button);
|
|
addView(button, 0, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.END | Gravity.CENTER_VERTICAL));
|
|
}
|
|
}
|
|
|
|
public void setTimer() {
|
|
timerView = new TimerView(getContext(), resourcesProvider);
|
|
timerView.timeLeft = 5000;
|
|
addView(timerView, LayoutHelper.createFrameRelatively(20, 20, Gravity.START | Gravity.CENTER_VERTICAL, 21, 0, 21, 0));
|
|
}
|
|
}
|
|
|
|
public static class SimpleLayout extends ButtonLayout {
|
|
|
|
public final ImageView imageView;
|
|
public final TextView textView;
|
|
|
|
public SimpleLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context, resourcesProvider);
|
|
|
|
final int undoInfoColor = getThemedColor(Theme.key_undo_infoColor);
|
|
|
|
imageView = new ImageView(context);
|
|
imageView.setColorFilter(new PorterDuffColorFilter(undoInfoColor, PorterDuff.Mode.MULTIPLY));
|
|
addView(imageView, LayoutHelper.createFrameRelatively(24, 24, Gravity.START | Gravity.CENTER_VERTICAL, 16, 12, 16, 12));
|
|
|
|
textView = new TextView(context);
|
|
textView.setSingleLine();
|
|
textView.setTextColor(undoInfoColor);
|
|
textView.setTypeface(Typeface.SANS_SERIF);
|
|
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
|
addView(textView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 56, 0, 16, 0));
|
|
}
|
|
|
|
public CharSequence getAccessibilityText() {
|
|
return textView.getText();
|
|
}
|
|
}
|
|
|
|
@SuppressLint("ViewConstructor")
|
|
public static class MultiLineLayout extends ButtonLayout {
|
|
|
|
public final BackupImageView imageView = new BackupImageView(getContext());
|
|
public final TextView textView = new TextView(getContext());
|
|
|
|
public MultiLineLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context, resourcesProvider);
|
|
addView(imageView, LayoutHelper.createFrameRelatively(30, 30, Gravity.START | Gravity.CENTER_VERTICAL, 12, 8, 12, 8));
|
|
|
|
textView.setGravity(Gravity.START);
|
|
textView.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8));
|
|
textView.setTextColor(getThemedColor(Theme.key_undo_infoColor));
|
|
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
|
textView.setTypeface(Typeface.SANS_SERIF);
|
|
addView(textView, LayoutHelper.createFrameRelatively(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 56, 0, 16, 0));
|
|
}
|
|
|
|
public CharSequence getAccessibilityText() {
|
|
return textView.getText();
|
|
}
|
|
}
|
|
|
|
@SuppressLint("ViewConstructor")
|
|
public static class TwoLineLayout extends ButtonLayout {
|
|
|
|
public final BackupImageView imageView;
|
|
public final TextView titleTextView;
|
|
public final TextView subtitleTextView;
|
|
|
|
public TwoLineLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context, resourcesProvider);
|
|
|
|
final int undoInfoColor = getThemedColor(Theme.key_undo_infoColor);
|
|
|
|
addView(imageView = new BackupImageView(context), LayoutHelper.createFrameRelatively(29, 29, Gravity.START | Gravity.CENTER_VERTICAL, 12, 12, 12, 12));
|
|
|
|
final LinearLayout linearLayout = new LinearLayout(context);
|
|
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
|
addView(linearLayout, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 54, 8, 12, 8));
|
|
|
|
titleTextView = new TextView(context);
|
|
titleTextView.setSingleLine();
|
|
titleTextView.setTextColor(undoInfoColor);
|
|
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
|
titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
|
linearLayout.addView(titleTextView);
|
|
|
|
subtitleTextView = new TextView(context);
|
|
subtitleTextView.setMaxLines(2);
|
|
subtitleTextView.setTextColor(undoInfoColor);
|
|
subtitleTextView.setLinkTextColor(getThemedColor(Theme.key_undo_cancelColor));
|
|
subtitleTextView.setMovementMethod(new LinkMovementMethod());
|
|
subtitleTextView.setTypeface(Typeface.SANS_SERIF);
|
|
subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
|
|
linearLayout.addView(subtitleTextView);
|
|
}
|
|
|
|
public CharSequence getAccessibilityText() {
|
|
return titleTextView.getText() + ".\n" + subtitleTextView.getText();
|
|
}
|
|
}
|
|
|
|
public static class TwoLineLottieLayout extends ButtonLayout {
|
|
|
|
public final RLottieImageView imageView;
|
|
public final TextView titleTextView;
|
|
public final TextView subtitleTextView;
|
|
|
|
private final int textColor;
|
|
|
|
public TwoLineLottieLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context, resourcesProvider);
|
|
this.textColor = getThemedColor(Theme.key_undo_infoColor);
|
|
setBackground(getThemedColor(Theme.key_undo_background));
|
|
|
|
imageView = new RLottieImageView(context);
|
|
imageView.setScaleType(ImageView.ScaleType.CENTER);
|
|
addView(imageView, LayoutHelper.createFrameRelatively(56, 48, Gravity.START | Gravity.CENTER_VERTICAL));
|
|
|
|
final int undoInfoColor = getThemedColor(Theme.key_undo_infoColor);
|
|
final int undoLinkColor = getThemedColor(Theme.key_voipgroup_overlayBlue1);
|
|
|
|
final LinearLayout linearLayout = new LinearLayout(context);
|
|
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
|
addView(linearLayout, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 56, 8, 12, 8));
|
|
|
|
titleTextView = new TextView(context);
|
|
titleTextView.setSingleLine();
|
|
titleTextView.setTextColor(undoInfoColor);
|
|
titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
|
titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
|
linearLayout.addView(titleTextView);
|
|
|
|
subtitleTextView = new TextView(context);
|
|
subtitleTextView.setTextColor(undoInfoColor);
|
|
subtitleTextView.setLinkTextColor(undoLinkColor);
|
|
subtitleTextView.setTypeface(Typeface.SANS_SERIF);
|
|
subtitleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
|
|
linearLayout.addView(subtitleTextView);
|
|
}
|
|
|
|
@Override
|
|
protected void onShow() {
|
|
super.onShow();
|
|
imageView.playAnimation();
|
|
}
|
|
|
|
public void setAnimation(int resId, String... layers) {
|
|
setAnimation(resId, 32, 32, layers);
|
|
}
|
|
|
|
public void setAnimation(int resId, int w, int h, String... layers) {
|
|
imageView.setAnimation(resId, w, h);
|
|
for (String layer : layers) {
|
|
imageView.setLayerColor(layer + ".**", textColor);
|
|
}
|
|
}
|
|
|
|
public CharSequence getAccessibilityText() {
|
|
return titleTextView.getText() + ".\n" + subtitleTextView.getText();
|
|
}
|
|
}
|
|
|
|
public static class LottieLayout extends ButtonLayout {
|
|
|
|
public RLottieImageView imageView;
|
|
public LinkSpanDrawable.LinksTextView textView;
|
|
|
|
private int textColor;
|
|
|
|
public LottieLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context, resourcesProvider);
|
|
|
|
imageView = new RLottieImageView(context);
|
|
imageView.setScaleType(ImageView.ScaleType.CENTER);
|
|
addView(imageView, LayoutHelper.createFrameRelatively(56, 48, Gravity.START | Gravity.CENTER_VERTICAL));
|
|
|
|
textView = new LinkSpanDrawable.LinksTextView(context);
|
|
textView.setDisablePaddingsOffset(true);
|
|
textView.setSingleLine();
|
|
textView.setTypeface(Typeface.SANS_SERIF);
|
|
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
|
textView.setEllipsize(TextUtils.TruncateAt.END);
|
|
textView.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8));
|
|
addView(textView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 56, 0, 8, 0));
|
|
|
|
textView.setLinkTextColor(getThemedColor(Theme.key_undo_cancelColor));
|
|
setTextColor(getThemedColor(Theme.key_undo_infoColor));
|
|
setBackground(getThemedColor(Theme.key_undo_background));
|
|
}
|
|
|
|
public LottieLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider, int backgroundColor, int textColor) {
|
|
this(context, resourcesProvider);
|
|
setBackground(backgroundColor);
|
|
setTextColor(textColor);
|
|
}
|
|
|
|
public void setTextColor(int textColor) {
|
|
this.textColor = textColor;
|
|
textView.setTextColor(textColor);
|
|
}
|
|
|
|
@Override
|
|
protected void onShow() {
|
|
super.onShow();
|
|
imageView.playAnimation();
|
|
}
|
|
|
|
public void setAnimation(int resId, String... layers) {
|
|
setAnimation(resId, 32, 32, layers);
|
|
}
|
|
|
|
public void setAnimation(int resId, int w, int h, String... layers) {
|
|
imageView.setAnimation(resId, w, h);
|
|
for (String layer : layers) {
|
|
imageView.setLayerColor(layer + ".**", textColor);
|
|
}
|
|
}
|
|
|
|
public void setAnimation(TLRPC.Document document, int w, int h, String... layers) {
|
|
imageView.setAnimation(document, w, h);
|
|
for (String layer : layers) {
|
|
imageView.setLayerColor(layer + ".**", textColor);
|
|
}
|
|
}
|
|
|
|
public void setIconPaddingBottom(int paddingBottom) {
|
|
imageView.setLayoutParams(LayoutHelper.createFrameRelatively(56, 48 - paddingBottom, Gravity.START | Gravity.CENTER_VERTICAL, 0, 0, 0, paddingBottom));
|
|
}
|
|
|
|
public CharSequence getAccessibilityText() {
|
|
return textView.getText();
|
|
}
|
|
}
|
|
|
|
public static class UsersLayout extends ButtonLayout {
|
|
|
|
public AvatarsImageView avatarsImageView;
|
|
public TextView textView;
|
|
|
|
public UsersLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context, resourcesProvider);
|
|
|
|
avatarsImageView = new AvatarsImageView(context, false);
|
|
avatarsImageView.setStyle(AvatarsDrawable.STYLE_MESSAGE_SEEN);
|
|
avatarsImageView.setAvatarsTextSize(AndroidUtilities.dp(18));
|
|
addView(avatarsImageView, LayoutHelper.createFrameRelatively(24 + 12 + 12 + 8, 48, Gravity.START | Gravity.CENTER_VERTICAL, 12, 0, 0, 0));
|
|
|
|
textView = new LinkSpanDrawable.LinksTextView(context);
|
|
textView.setTypeface(Typeface.SANS_SERIF);
|
|
textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
|
textView.setEllipsize(TextUtils.TruncateAt.END);
|
|
textView.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8));
|
|
addView(textView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL, 12 + 56 + 2, 0, 8, 0));
|
|
|
|
textView.setLinkTextColor(getThemedColor(Theme.key_undo_cancelColor));
|
|
setTextColor(getThemedColor(Theme.key_undo_infoColor));
|
|
setBackground(getThemedColor(Theme.key_undo_background));
|
|
}
|
|
|
|
public UsersLayout(@NonNull Context context, Theme.ResourcesProvider resourcesProvider, int backgroundColor, int textColor) {
|
|
this(context, resourcesProvider);
|
|
setBackground(backgroundColor);
|
|
setTextColor(textColor);
|
|
}
|
|
|
|
public void setTextColor(int textColor) {
|
|
textView.setTextColor(textColor);
|
|
}
|
|
|
|
@Override
|
|
protected void onShow() {
|
|
super.onShow();
|
|
}
|
|
|
|
public CharSequence getAccessibilityText() {
|
|
return textView.getText();
|
|
}
|
|
}
|
|
//endregion
|
|
|
|
//region Buttons
|
|
@SuppressLint("ViewConstructor")
|
|
public abstract static class Button extends FrameLayout implements Layout.Callback {
|
|
|
|
public Button(@NonNull Context context) {
|
|
super(context);
|
|
}
|
|
|
|
@Override
|
|
public void onAttach(@NonNull Layout layout, @NonNull Bulletin bulletin) {
|
|
}
|
|
|
|
@Override
|
|
public void onDetach(@NonNull Layout layout) {
|
|
}
|
|
|
|
@Override
|
|
public void onShow(@NonNull Layout layout) {
|
|
}
|
|
|
|
@Override
|
|
public void onHide(@NonNull Layout layout) {
|
|
}
|
|
|
|
@Override
|
|
public void onEnterTransitionStart(@NonNull Layout layout) {
|
|
}
|
|
|
|
@Override
|
|
public void onEnterTransitionEnd(@NonNull Layout layout) {
|
|
}
|
|
|
|
@Override
|
|
public void onExitTransitionStart(@NonNull Layout layout) {
|
|
}
|
|
|
|
@Override
|
|
public void onExitTransitionEnd(@NonNull Layout layout) {
|
|
}
|
|
}
|
|
|
|
@SuppressLint("ViewConstructor")
|
|
public static final class UndoButton extends Button {
|
|
|
|
private final Theme.ResourcesProvider resourcesProvider;
|
|
private Runnable undoAction;
|
|
private Runnable delayedAction;
|
|
|
|
private Bulletin bulletin;
|
|
private TextView undoTextView;
|
|
private boolean isUndone;
|
|
|
|
public UndoButton(@NonNull Context context, boolean text) {
|
|
this(context, text, null);
|
|
}
|
|
|
|
public UndoButton(@NonNull Context context, boolean text, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context);
|
|
this.resourcesProvider = resourcesProvider;
|
|
|
|
final int undoCancelColor = getThemedColor(Theme.key_undo_cancelColor);
|
|
|
|
if (text) {
|
|
undoTextView = new TextView(context);
|
|
undoTextView.setOnClickListener(v -> undo());
|
|
final int leftInset = LocaleController.isRTL ? AndroidUtilities.dp(16) : 0;
|
|
final int rightInset = LocaleController.isRTL ? 0 : AndroidUtilities.dp(16);
|
|
undoTextView.setBackground(Theme.createCircleSelectorDrawable((undoCancelColor & 0x00ffffff) | 0x19000000, leftInset, rightInset));
|
|
undoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
|
undoTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
|
undoTextView.setTextColor(undoCancelColor);
|
|
undoTextView.setText(LocaleController.getString("Undo", R.string.Undo));
|
|
undoTextView.setGravity(Gravity.CENTER_VERTICAL);
|
|
ViewHelper.setPaddingRelative(undoTextView, 16, 0, 16, 0);
|
|
addView(undoTextView, LayoutHelper.createFrameRelatively(LayoutHelper.WRAP_CONTENT, 48, Gravity.CENTER_VERTICAL, 8, 0, 0, 0));
|
|
} else {
|
|
final ImageView undoImageView = new ImageView(getContext());
|
|
undoImageView.setOnClickListener(v -> undo());
|
|
undoImageView.setImageResource(R.drawable.chats_undo);
|
|
undoImageView.setColorFilter(new PorterDuffColorFilter(undoCancelColor, PorterDuff.Mode.MULTIPLY));
|
|
undoImageView.setBackground(Theme.createSelectorDrawable((undoCancelColor & 0x00ffffff) | 0x19000000));
|
|
ViewHelper.setPaddingRelative(undoImageView, 0, 12, 0, 12);
|
|
addView(undoImageView, LayoutHelper.createFrameRelatively(56, 48, Gravity.CENTER_VERTICAL));
|
|
}
|
|
}
|
|
|
|
public UndoButton setText(CharSequence text) {
|
|
if (undoTextView != null) {
|
|
undoTextView.setText(text);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public void undo() {
|
|
if (bulletin != null) {
|
|
isUndone = true;
|
|
if (undoAction != null) {
|
|
undoAction.run();
|
|
}
|
|
bulletin.hide();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onAttach(@NonNull Layout layout, @NonNull Bulletin bulletin) {
|
|
this.bulletin = bulletin;
|
|
}
|
|
|
|
@Override
|
|
public void onDetach(@NonNull Layout layout) {
|
|
this.bulletin = null;
|
|
if (delayedAction != null && !isUndone) {
|
|
delayedAction.run();
|
|
}
|
|
}
|
|
|
|
public UndoButton setUndoAction(Runnable undoAction) {
|
|
this.undoAction = undoAction;
|
|
return this;
|
|
}
|
|
|
|
public UndoButton setDelayedAction(Runnable delayedAction) {
|
|
this.delayedAction = delayedAction;
|
|
return this;
|
|
}
|
|
|
|
private int getThemedColor(String key) {
|
|
Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null;
|
|
return color != null ? color : Theme.getColor(key);
|
|
}
|
|
}
|
|
//endregion
|
|
|
|
public static class EmptyBulletin extends Bulletin {
|
|
|
|
public EmptyBulletin() {
|
|
super();
|
|
}
|
|
|
|
@Override
|
|
public Bulletin show() {
|
|
return this;
|
|
}
|
|
}
|
|
|
|
private static class TimerView extends View {
|
|
|
|
private final Paint progressPaint;
|
|
private long timeLeft;
|
|
private int prevSeconds;
|
|
private String timeLeftString;
|
|
private int textWidth;
|
|
|
|
StaticLayout timeLayout;
|
|
StaticLayout timeLayoutOut;
|
|
int textWidthOut;
|
|
|
|
float timeReplaceProgress = 1f;
|
|
|
|
private TextPaint textPaint;
|
|
private long lastUpdateTime;
|
|
RectF rect = new RectF();
|
|
|
|
public TimerView(Context context, Theme.ResourcesProvider resourcesProvider) {
|
|
super(context);
|
|
|
|
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
|
|
textPaint.setTextSize(AndroidUtilities.dp(12));
|
|
textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
|
|
textPaint.setColor(Theme.getColor(Theme.key_undo_infoColor, resourcesProvider));
|
|
|
|
progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
progressPaint.setStyle(Paint.Style.STROKE);
|
|
progressPaint.setStrokeWidth(AndroidUtilities.dp(2));
|
|
progressPaint.setStrokeCap(Paint.Cap.ROUND);
|
|
progressPaint.setColor(Theme.getColor(Theme.key_undo_infoColor, resourcesProvider));
|
|
}
|
|
|
|
@Override
|
|
protected void onDraw(Canvas canvas) {
|
|
super.onDraw(canvas);
|
|
int newSeconds = timeLeft > 0 ? (int) Math.ceil(timeLeft / 1000.0f) : 0;
|
|
rect.set(AndroidUtilities.dp(1), AndroidUtilities.dp(1), getMeasuredWidth() - AndroidUtilities.dp(1), getMeasuredHeight() - AndroidUtilities.dp(1));
|
|
if (prevSeconds != newSeconds) {
|
|
prevSeconds = newSeconds;
|
|
timeLeftString = String.format("%d", Math.max(0, newSeconds));
|
|
if (timeLayout != null) {
|
|
timeLayoutOut = timeLayout;
|
|
timeReplaceProgress = 0;
|
|
textWidthOut = textWidth;
|
|
}
|
|
textWidth = (int) Math.ceil(textPaint.measureText(timeLeftString));
|
|
timeLayout = new StaticLayout(timeLeftString, textPaint, Integer.MAX_VALUE, android.text.Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
|
|
}
|
|
|
|
if (timeReplaceProgress < 1f) {
|
|
timeReplaceProgress += 16f / 150f;
|
|
if (timeReplaceProgress > 1f) {
|
|
timeReplaceProgress = 1f;
|
|
} else {
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
int alpha = textPaint.getAlpha();
|
|
|
|
if (timeLayoutOut != null && timeReplaceProgress < 1f) {
|
|
textPaint.setAlpha((int) (alpha * (1f - timeReplaceProgress)));
|
|
canvas.save();
|
|
canvas.translate(rect.centerX() - textWidthOut / 2f, rect.centerY() - timeLayoutOut.getHeight() / 2f + AndroidUtilities.dp(10) * timeReplaceProgress);
|
|
timeLayoutOut.draw(canvas);
|
|
textPaint.setAlpha(alpha);
|
|
canvas.restore();
|
|
}
|
|
|
|
if (timeLayout != null) {
|
|
if (timeReplaceProgress != 1f) {
|
|
textPaint.setAlpha((int) (alpha * timeReplaceProgress));
|
|
}
|
|
canvas.save();
|
|
canvas.translate(rect.centerX() - textWidth / 2f, rect.centerY() - timeLayout.getHeight() / 2f - AndroidUtilities.dp(10) * (1f - timeReplaceProgress));
|
|
timeLayout.draw(canvas);
|
|
if (timeReplaceProgress != 1f) {
|
|
textPaint.setAlpha(alpha);
|
|
}
|
|
canvas.restore();
|
|
}
|
|
|
|
canvas.drawArc(rect, -90, -360 * (Math.max(0, timeLeft) / 5000.0f), false, progressPaint);
|
|
|
|
if (lastUpdateTime != 0) {
|
|
long newTime = System.currentTimeMillis();
|
|
long dt = newTime - lastUpdateTime;
|
|
timeLeft -= dt;
|
|
lastUpdateTime = newTime;
|
|
} else {
|
|
lastUpdateTime = System.currentTimeMillis();
|
|
}
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
// to make bulletin above everything
|
|
// use as BulletinFactory.of(BulletinWindow.make(context), resourcesProvider)...
|
|
public static class BulletinWindow extends Dialog {
|
|
public static FrameLayout make(Context context) {
|
|
return new BulletinWindow(context).container;
|
|
}
|
|
|
|
private final FrameLayout container;
|
|
private BulletinWindow(Context context) {
|
|
super(context);
|
|
setContentView(
|
|
container = new FrameLayout(context) {
|
|
@Override
|
|
public void addView(View child) {
|
|
super.addView(child);
|
|
BulletinWindow.this.show();
|
|
}
|
|
|
|
@Override
|
|
public void removeView(View child) {
|
|
super.removeView(child);
|
|
BulletinWindow.this.dismiss();
|
|
removeDelegate(container);
|
|
}
|
|
},
|
|
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
|
);
|
|
|
|
addDelegate(container, new Delegate() {
|
|
@Override
|
|
public int getBottomOffset(int tag) {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public int getTopOffset(int tag) {
|
|
return AndroidUtilities.statusBarHeight;
|
|
}
|
|
});
|
|
|
|
try {
|
|
Window window = getWindow();
|
|
window.setWindowAnimations(R.style.DialogNoAnimation);
|
|
window.setBackgroundDrawable(null);
|
|
WindowManager.LayoutParams params = window.getAttributes();
|
|
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
|
params.gravity = Gravity.TOP | Gravity.LEFT;
|
|
params.dimAmount = 0;
|
|
params.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
|
|
params.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
params.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
|
|
}
|
|
params.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
|
|
if (Build.VERSION.SDK_INT >= 21) {
|
|
params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
|
|
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
|
|
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
|
|
}
|
|
params.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN;
|
|
params.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
|
if (Build.VERSION.SDK_INT >= 28) {
|
|
params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
|
}
|
|
window.setAttributes(params);
|
|
} catch (Exception ignore) {}
|
|
}
|
|
}
|
|
}
|