package im.tox.toktok.app.message_activity; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Px; import androidx.core.view.ViewCompat; import androidx.customview.widget.ViewDragHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import im.tox.toktok.R; final class SlideInAttachmentsLayout extends ViewGroup { private static final Logger logger = LoggerFactory.getLogger(SlideInAttachmentsLayout.class); @NonNull private final ViewDragHelper mDragHelper; private View mChild; private float mInitialMotionY; private int mDragRange; private int mTop; private float mDragOffset; public SlideInAttachmentsLayout( Context context, AttributeSet attrs, int defStyle ) { super(context, attrs, defStyle); mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback()); } public SlideInAttachmentsLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SlideInAttachmentsLayout(Context context) { this(context, null); } @Override protected void onFinishInflate() { mChild = this.findViewById(R.id.fragment_attachments); super.onFinishInflate(); } private void smoothSlideTo(float slideOffset) { int topBound = getHeight() - mChild.getHeight(); int y = (int) (topBound + slideOffset * mDragRange); if (mDragHelper.smoothSlideViewTo(mChild, mChild.getLeft(), y)) { ViewCompat.postInvalidateOnAnimation(this); } } @Override public void computeScroll() { if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } @Override public boolean onInterceptTouchEvent(@NonNull MotionEvent ev) { float y = ev.getY(); switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: logger.debug("DOWN"); mInitialMotionY = y; break; case MotionEvent.ACTION_UP: logger.debug("UP"); return true; } return mDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(@NonNull MotionEvent ev) { try { mDragHelper.processTouchEvent(ev); return true; } catch (Exception ex) { return false; } } void start() { setVisibility(View.VISIBLE); smoothSlideTo(0f); } void finish() { smoothSlideTo(1f); new Handler().postDelayed(() -> setVisibility(View.INVISIBLE), 500); } @Override public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { float y = ev.getY(); switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: mInitialMotionY = y; if (y < getHeight() - mChild.getHeight()) { finish(); } break; case MotionEvent.ACTION_UP: float dy = y - mInitialMotionY; if ((dy > 0 && (dy / mChild.getHeight()) > 0.3) || dy == 0) { finish(); } else { smoothSlideTo(0f); } break; } return super.dispatchTouchEvent(ev); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChildren(widthMeasureSpec, heightMeasureSpec); int maxWidth = MeasureSpec.getSize(widthMeasureSpec); int maxHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(View.resolveSizeAndState(maxWidth, widthMeasureSpec, 0), View.resolveSizeAndState(maxHeight, heightMeasureSpec, 0)); } @Override protected void onLayout(boolean changed, int l, int t, @Px int r, int b) { mDragRange = mChild.getHeight(); if (changed) { mTop = getHeight() - mChild.getHeight(); mChild.layout(0, getHeight() - mChild.getHeight(), r, getHeight()); } else { mChild.layout(0, mTop, r, mTop + mChild.getMeasuredHeight()); } } private final class DragHelperCallback extends ViewDragHelper.Callback { @Override public boolean tryCaptureView(@NonNull View child, int pointerId) { return child == mChild; } @Override public void onViewPositionChanged(@NonNull View changedView, int left, int top, @Px int dx, @Px int dy) { mTop = top; mDragOffset = (float) top / mDragRange; requestLayout(); } @Override public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { int top = getPaddingTop(); if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) { top += mDragRange; } } @Override public int getViewVerticalDragRange(@NonNull View child) { return mDragRange; } @Override public int clampViewPositionVertical(@NonNull View child, int top, int dy) { int topBound = getHeight() - mChild.getHeight(); int bottomBound = getHeight() + mChild.getPaddingTop(); return Math.min(Math.max(top, topBound), bottomBound); } } }