diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/mastodon/api/MediaResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/mastodon/api/MediaResources.java index 42c3f6fce..2491dd21d 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/mastodon/api/MediaResources.java +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/mastodon/api/MediaResources.java @@ -25,9 +25,6 @@ import org.mariotaku.restfu.annotation.param.Param; import org.mariotaku.restfu.http.BodyType; import org.mariotaku.restfu.http.mime.Body; -/** - * Created by mariotaku on 2017/4/17. - */ public interface MediaResources { diff --git a/twidere/src/main/java/android/support/v7/widget/FixedLinearLayoutManager.java b/twidere/src/main/java/android/support/v7/widget/FixedLinearLayoutManager.java deleted file mode 100644 index c52ccdc3b..000000000 --- a/twidere/src/main/java/android/support/v7/widget/FixedLinearLayoutManager.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2015 Mariotaku Lee - * - * 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 . - */ - -package android.support.v7.widget; - -import android.content.Context; -import android.view.View; - -/** - * Created by mariotaku on 15/3/24. - */ -public class FixedLinearLayoutManager extends LinearLayoutManager { - - public FixedLinearLayoutManager(Context context) { - super(context); - } - - public FixedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { - super(context, orientation, reverseLayout); - } - - @Override - View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible, boolean acceptPartiallyVisible) { - // XXX Fixed NPE by add a simple check to child view count - if (getChildCount() < 0) return null; - return super.findOneVisibleChild(fromIndex, toIndex, completelyVisible, acceptPartiallyVisible); - } - -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/SimpleDrawerCallback.java b/twidere/src/main/java/org/mariotaku/twidere/util/SimpleDrawerCallback.java deleted file mode 100644 index de03803cf..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/util/SimpleDrawerCallback.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.util; - -import android.os.SystemClock; -import android.support.v7.widget.RecyclerView; -import android.view.MotionEvent; -import android.view.View; - -import org.mariotaku.twidere.view.HeaderDrawerLayout.DrawerCallback; - -/** -* Created by mariotaku on 14/12/2. -*/ -public class SimpleDrawerCallback implements DrawerCallback { - - private final RecyclerView mRecyclerView; - - public SimpleDrawerCallback(RecyclerView recyclerView) { - mRecyclerView = recyclerView; - } - - - @Override - public void fling(float velocity) { - mRecyclerView.fling(0, (int) velocity); - } - - @Override - public void scrollBy(float dy) { - mRecyclerView.scrollBy(0, (int) dy); - } - - @Override - public boolean canScroll(float dy) { - return mRecyclerView.canScrollVertically((int) dy); - } - - @Override - public boolean isScrollContent(float x, float y) { - final View v = mRecyclerView; - final int[] location = new int[2]; - v.getLocationInWindow(location); - return x >= location[0] && x <= location[0] + v.getWidth() - && y >= location[1] && y <= location[1] + v.getHeight(); - } - - @Override - public void cancelTouch() { - mRecyclerView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), - SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0)); - } - - @Override - public boolean shouldLayoutHeaderBottom() { - return true; - } - - - @Override - public void topChanged(int offset) { - - } -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/support/ViewSupport.java b/twidere/src/main/java/org/mariotaku/twidere/util/support/ViewSupport.java index 03d5920e1..4ce4c5596 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/support/ViewSupport.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/support/ViewSupport.java @@ -23,6 +23,7 @@ import android.annotation.TargetApi; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.os.Build; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; @@ -172,7 +173,7 @@ public final class ViewSupport { view.setClipToOutline(clipToOutline); } - public static void setOutlineProvider(View view, ViewOutlineProviderCompat outlineProvider) { + public static void setOutlineProvider(View view, @Nullable ViewOutlineProviderCompat outlineProvider) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; view.setOutlineProvider(new ViewOutlineProviderCompat.ViewOutlineProviderL(outlineProvider)); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/support/view/ViewOutlineProviderCompat.java b/twidere/src/main/java/org/mariotaku/twidere/util/support/view/ViewOutlineProviderCompat.java index 1ae41caef..b549e9b69 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/support/view/ViewOutlineProviderCompat.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/support/view/ViewOutlineProviderCompat.java @@ -23,6 +23,7 @@ import android.annotation.TargetApi; import android.graphics.Outline; import android.graphics.drawable.Drawable; import android.os.Build; +import android.support.annotation.Nullable; import android.view.View; import android.view.ViewOutlineProvider; @@ -83,15 +84,18 @@ public abstract class ViewOutlineProviderCompat { @TargetApi(Build.VERSION_CODES.LOLLIPOP) public static class ViewOutlineProviderL extends ViewOutlineProvider { + @Nullable private final ViewOutlineProviderCompat providerCompat; - public ViewOutlineProviderL(ViewOutlineProviderCompat providerCompat) { + public ViewOutlineProviderL(@Nullable ViewOutlineProviderCompat providerCompat) { this.providerCompat = providerCompat; } @Override public void getOutline(View view, Outline outline) { - providerCompat.getOutline(view, new OutlineCompat.OutlineL(outline)); + if (providerCompat != null) { + providerCompat.getOutline(view, new OutlineCompat.OutlineL(outline)); + } } } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/HeaderDrawerLayout.java b/twidere/src/main/java/org/mariotaku/twidere/view/HeaderDrawerLayout.java deleted file mode 100644 index 89f465b58..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/view/HeaderDrawerLayout.java +++ /dev/null @@ -1,572 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.view; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.os.SystemClock; -import android.support.annotation.NonNull; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.ViewDragHelper; -import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.OverScroller; - -import org.mariotaku.twidere.R; - -import kotlin.ranges.RangesKt; - -/** - * Custom ViewGroup for user profile page like Google+ but with tab swipe - * - * @author mariotaku - */ -public class HeaderDrawerLayout extends ViewGroup { - - private final ViewDragHelper mDragHelper; - private final OverScroller mScroller; - private final GestureDetector mGestureDetector; - - private final InternalContainer mContainer; - private final DragCallback mDragCallback; - - private DrawerCallback mDrawerCallback; - private boolean mUsingDragHelper; - private boolean mScrollingHeaderByGesture, mScrollingContentCallback; - private boolean mTouchDown, mTouchingScrollableContent; - - private int mHeaderOffset; - private int mTop; - - public HeaderDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HeaderDrawerLayout); - final int headerLayoutId = a.getResourceId(R.styleable.HeaderDrawerLayout_hdl_headerLayout, 0); - final int contentLayoutId = a.getResourceId(R.styleable.HeaderDrawerLayout_hdl_contentLayout, 0); - addView(mContainer = new InternalContainer(this, context, headerLayoutId, contentLayoutId), - LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - a.recycle(); - mDragHelper = ViewDragHelper.create(this, mDragCallback = new DragCallback(this)); - mGestureDetector = new GestureDetector(context, new GestureListener(this)); - mScroller = new OverScroller(context); - } - - public HeaderDrawerLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public HeaderDrawerLayout(Context context) { - this(context, null); - } - - @Override - public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: { - mScroller.abortAnimation(); - mTouchDown = true; - mTouchingScrollableContent = isScrollContentCallback(ev.getX(), ev.getY()); - mUsingDragHelper = false; - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - mTouchDown = false; - mTouchingScrollableContent = false; - mUsingDragHelper = false; - } - } - mGestureDetector.onTouchEvent(ev); - return super.dispatchTouchEvent(ev); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mDragHelper.shouldInterceptTouchEvent(ev) || mScrollingHeaderByGesture) { - mUsingDragHelper = true; - return true; - } - return false; - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouchEvent(@NonNull MotionEvent event) { - mDragHelper.processTouchEvent(event); - return true; - } - - @Override - public boolean canScrollVertically(final int direction) { - if (direction > 0) { - return getHeaderTop() > getHeaderTopMaximum(); - } else if (direction < 0) { - return getHeaderTop() < getHeaderTopMaximum(); - } - return false; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final View child = getChildAt(0); - - final int childWidthMeasureSpec = makeChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight()); - final int childHeightMeasureSpec = makeChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom()); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - for (int i = 0, j = getChildCount(); i < j; i++) { - final View child = getChildAt(i); - final int left = getPaddingLeft(), right = left + child.getMeasuredWidth(); - final int top, bottom; - if (i == 0) { - if (shouldLayoutHeaderBottomCallback() && child.getHeight() != 0) { - // How far did we moved? - int delta = mHeaderOffset + getPaddingTop() - child.getTop(); - bottom = child.getBottom() + delta; - top = bottom - child.getHeight(); - } else { - top = mHeaderOffset + getPaddingTop(); - bottom = top + child.getMeasuredHeight(); - } - } else { - top = getChildAt(i - 1).getBottom(); - bottom = top + child.getMeasuredHeight(); - } - child.layout(left, top, right, bottom); - notifyOffsetChanged(); - } - } - - @Override - public void computeScroll() { - boolean invalidate = mDragHelper.continueSettling(true); - if (!mTouchDown && mScroller.computeScrollOffset()) { - if (!invalidate) { - offsetHeaderBy(mScroller.getCurrY() - getHeaderTop()); - } - invalidate = true; - } - updateViewOffset(); - if (invalidate) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - if (getChildCount() != 1) { - throw new IllegalArgumentException("Add subview by XML is not allowed."); - } - } - - public void flingHeader(float velocity) { - if (mTouchDown) { - mScroller.abortAnimation(); - return; - } - mScroller.fling(0, getHeaderTop(), 0, (int) velocity, 0, 0, - mContainer.getHeaderTopMinimum(), mContainer.getHeaderTopMaximum()); - ViewCompat.postInvalidateOnAnimation(this); - } - - public View getContent() { - return mContainer.getContent(); - } - - public View getHeader() { - return mContainer.getHeader(); - } - - public int getHeaderTop() { - return mContainer.getTop(); - } - - public int getHeaderTopMaximum() { - return mContainer.getHeaderTopMaximum(); - } - - public int getHeaderTopMinimum() { - return mContainer.getHeaderTopMinimum(); - } - - public void setDrawerCallback(DrawerCallback callback) { - mDrawerCallback = callback; - } - - private boolean canScrollCallback(float dy) { - return mDrawerCallback.canScroll(dy); - } - - private void cancelTouchCallback() { - mDrawerCallback.cancelTouch(); - } - - private void flingCallback(float velocity) { - mDrawerCallback.fling(velocity); - } - - private float getDragTouchSlop() { - return mDragHelper.getTouchSlop(); - } - - private int getScrollRange() { - return mContainer.getScrollRange(); - } - - private boolean isScrollContentCallback(float x, float y) { - return mDrawerCallback.isScrollContent(x, y); - } - - private boolean isScrollingContentCallback() { - return mScrollingContentCallback; - } - - private void setScrollingContentCallback(boolean scrolling) { - mScrollingContentCallback = scrolling; - } - - private boolean isScrollingHeaderByHelper() { - return mDragCallback.isScrollingHeaderByHelper(); - } - - private boolean isTouchingScrollableContent() { - return mTouchingScrollableContent; - } - - private boolean isUsingDragHelper() { - return mUsingDragHelper; - } - - private boolean isValidScroll(float direction, float other) { - return Math.abs(direction) > getDragTouchSlop() && Math.abs(direction) > Math.abs(other); - } - - private static int makeChildMeasureSpec(int spec, int padding) { - final int size = MeasureSpec.getSize(spec), mode = MeasureSpec.getMode(spec); - return MeasureSpec.makeMeasureSpec(size - padding, mode); - } - - private void notifyOffsetChanged() { - final int top = getHeaderTop(); - if (mTop == top) return; - mHeaderOffset = top - getPaddingTop(); - mDrawerCallback.topChanged(top); - mTop = top; - } - - private void offsetHeaderBy(int dy) { - final int prevTop = mContainer.getTop(); - final int clampedDy = RangesKt.coerceIn(prevTop + dy, getHeaderTopMinimum(), getHeaderTopMaximum()) - prevTop; - mContainer.offsetTopAndBottom(clampedDy); - } - - private void scrollByCallback(float dy) { - final int top = getHeaderTop(); - setScrollingContentCallback(top > getHeaderTopMinimum() && top < getHeaderTopMaximum()); - mDrawerCallback.scrollBy(dy); - } - - private void setScrollingHeaderByGesture(boolean scrolling) { - mScrollingHeaderByGesture = scrolling; - } - - private boolean shouldLayoutHeaderBottomCallback() { - if (mDragCallback == null || isInEditMode()) return false; - return mDrawerCallback.shouldLayoutHeaderBottom(); - } - - private void updateViewOffset() { - } - - public interface DrawerCallback { - - boolean canScroll(float dy); - - void cancelTouch(); - - void fling(float velocity); - - boolean isScrollContent(float x, float y); - - void scrollBy(float dy); - - boolean shouldLayoutHeaderBottom(); - - void topChanged(int offset); - } - - private static class DragCallback extends ViewDragHelper.Callback { - - private final HeaderDrawerLayout mDrawer; - private long mTime; - private float mDx, mDy, mVelocity; - private boolean mScrollingHeaderByHelper; - - public DragCallback(HeaderDrawerLayout drawer) { - mDrawer = drawer; - mTime = -1; - mDx = Float.NaN; - mDy = Float.NaN; - mVelocity = Float.NaN; - } - - @Override - public void onViewDragStateChanged(int state) { - switch (state) { - case ViewDragHelper.STATE_SETTLING: - case ViewDragHelper.STATE_DRAGGING: { - mScrollingHeaderByHelper = false; - break; - } - case ViewDragHelper.STATE_IDLE: { - if (mTime > 0 && !Float.isNaN(mVelocity)) { - final float velocity = mVelocity; - if (velocity < 0 && mDrawer.getHeaderTop() <= mDrawer.getHeaderTopMinimum()) { - mDrawer.flingCallback(-velocity); - } - } - mTime = -1; - mDx = Float.NaN; - mDy = Float.NaN; - mVelocity = Float.NaN; - mScrollingHeaderByHelper = false; - break; - } - } - super.onViewDragStateChanged(state); - } - - @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - super.onViewPositionChanged(changedView, left, top, dx, dy); - final long time = SystemClock.uptimeMillis(); - final float timeDelta = time - mTime; - mVelocity = mDy / timeDelta * 1000; - mTime = time; - mDy = dy; - } - - @Override - public void onViewReleased(View releasedChild, float xVel, float yVel) { - mDrawer.mDragHelper.flingCapturedView(mDrawer.getPaddingLeft(), - mDrawer.getHeaderTopMinimum(), mDrawer.getPaddingLeft(), - mDrawer.getHeaderTopMaximum()); - ViewCompat.postInvalidateOnAnimation(mDrawer); - } - - @Override - public int getViewVerticalDragRange(View child) { - return mDrawer.getScrollRange(); - } - - @Override - public boolean tryCaptureView(View view, int pointerId) { - return view == mDrawer.mContainer; - } - - @Override - public int clampViewPositionHorizontal(View child, int left, int dx) { - mDx = dx; - return mDrawer.getPaddingLeft(); - } - - @Override - public int clampViewPositionVertical(final View child, final int top, final int dy) { - final int current = mDrawer.getHeaderTop(); - if (!Float.isNaN(mDx) && mDrawer.isValidScroll(mDx, dy)) { - mScrollingHeaderByHelper = false; - return current; - } - if (dy > 0 && mDrawer.canScrollCallback(-dy) && mDrawer.isTouchingScrollableContent()) { - if (!mDrawer.isUsingDragHelper()) { - // Scrolling up while list still has space to scroll, so make header still - mScrollingHeaderByHelper = false; - return current; - } else { - mDrawer.scrollByCallback(-dy); - mScrollingHeaderByHelper = false; - return current; - } - } - final int min = mDrawer.getHeaderTopMinimum(), max = mDrawer.getHeaderTopMaximum(); - if (top < min && mDrawer.isTouchingScrollableContent() && mDrawer.isUsingDragHelper()) { - mDrawer.scrollByCallback(-dy); - } - mScrollingHeaderByHelper = true; - return RangesKt.coerceIn(top, min, max); - } - - private boolean isScrollingHeaderByHelper() { - return mScrollingHeaderByHelper; - } - } - - private static class GestureListener extends SimpleOnGestureListener { - - private final HeaderDrawerLayout mDrawer; - - public GestureListener(HeaderDrawerLayout drawer) { - mDrawer = drawer; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (!mDrawer.isUsingDragHelper() && mDrawer.isValidScroll(distanceY, distanceX)) { - final int offset = mDrawer.getHeaderTop(), min = mDrawer.getHeaderTopMinimum(); - if (!mDrawer.canScrollCallback(-1)) { - if (distanceY < 0) { - if (!mDrawer.isScrollingHeaderByHelper()) { - mDrawer.offsetHeaderBy(Math.round(-distanceY)); - } - mDrawer.setScrollingHeaderByGesture(true); - } else if (distanceY > 0 && offset > min) { - // Scrolling up when scrolling to list top, so we cancel touch event and scrolling header up - mDrawer.cancelTouchCallback(); - if (!mDrawer.isScrollingHeaderByHelper()) { - mDrawer.offsetHeaderBy(Math.round(-distanceY)); - } - } else if (offset <= min) { - mDrawer.scrollByCallback(-distanceX); - } - } - } - return true; - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - final int top = mDrawer.getHeaderTop(), min = mDrawer.getHeaderTopMinimum(); - final boolean showingFullContent = top <= min, flingUp = velocityY < 0; - final boolean verticalFling = Math.abs(velocityY) > Math.abs(velocityX); - if (!verticalFling) return true; - if (showingFullContent) { - if (flingUp) { - // Fling list up when showing full content - if (mDrawer.isScrollingContentCallback()) { - mDrawer.flingCallback(-velocityY); - } - } else { - // Fling down when list reached top and not dragging user ViewDragHelper, - // so we fling header down here - if (!mDrawer.canScrollCallback(1) && !mDrawer.isUsingDragHelper()) { - mDrawer.flingHeader(velocityY); - } - } - } else { - // Header still visible - } - return true; - } - - @Override - public boolean onDown(MotionEvent e) { - mDrawer.setScrollingHeaderByGesture(false); - mDrawer.setScrollingContentCallback(false); - return true; - } - } - - @SuppressLint("ViewConstructor") - private static class InternalContainer extends ViewGroup { - - private final HeaderDrawerLayout mParent; - private final View mHeaderView, mContentView; - - public InternalContainer(HeaderDrawerLayout parent, Context context, int headerLayoutId, int contentLayoutId) { - super(context); - mParent = parent; - final LayoutInflater inflater = LayoutInflater.from(context); - addView(mHeaderView = inflater.inflate(headerLayoutId, this, false)); - addView(mContentView = inflater.inflate(contentLayoutId, this, false)); - } - - public View getContent() { - return mContentView; - } - - public View getHeader() { - return mHeaderView; - } - - public int getHeaderTopMaximum() { - return mParent.getPaddingTop(); - } - - public int getHeaderTopMinimum() { - return mParent.getPaddingTop() - mHeaderView.getHeight(); - } - - public int getScrollRange() { - return getHeaderTopMaximum() - getHeaderTopMinimum(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int measureHeight = MeasureSpec.getSize(heightMeasureSpec); - int heightSum = 0; - for (int i = 0, j = getChildCount(); i < j; i++) { - final View child = getChildAt(i); - final LayoutParams lp = child.getLayoutParams(); - final int childHeightSpec; - if (lp.height == LayoutParams.MATCH_PARENT) { - childHeightSpec = heightMeasureSpec; - } else if (lp.height == LayoutParams.WRAP_CONTENT) { - childHeightSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.UNSPECIFIED); - } else { - childHeightSpec = MeasureSpec.makeMeasureSpec(measureHeight, MeasureSpec.EXACTLY); - } - child.measure(widthMeasureSpec, childHeightSpec); - heightSum += child.getMeasuredHeight(); - } - final int hSpec = MeasureSpec.makeMeasureSpec(heightSum, MeasureSpec.EXACTLY); - super.onMeasure(widthMeasureSpec, hSpec); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - for (int i = 0, j = getChildCount(); i < j; i++) { - final View child = getChildAt(i); - final int left = getPaddingLeft(), right = left + child.getMeasuredWidth(); - final int top = i == 0 ? getPaddingTop() : getChildAt(i - 1).getBottom(); - final int bottom = top + child.getMeasuredHeight(); - child.layout(left, top, right, bottom); - } - } - - @Override - public void offsetTopAndBottom(int offset) { - super.offsetTopAndBottom(offset); - mParent.notifyOffsetChanged(); - } - } - -} \ No newline at end of file diff --git a/twidere/src/main/kotlin/android/support/design/widget/AccessorHeaderBehavior.kt b/twidere/src/main/kotlin/android/support/design/widget/AccessorHeaderBehavior.kt index 0a084b877..a870daa36 100644 --- a/twidere/src/main/kotlin/android/support/design/widget/AccessorHeaderBehavior.kt +++ b/twidere/src/main/kotlin/android/support/design/widget/AccessorHeaderBehavior.kt @@ -22,9 +22,13 @@ package android.support.design.widget import android.content.Context import android.util.AttributeSet import android.view.View +import android.widget.OverScroller internal open class AccessorHeaderBehavior(context: Context, attrs: AttributeSet? = null) : HeaderBehavior(context, attrs) { + internal val scroller: OverScroller? + get() = mScroller + internal override fun getScrollRangeForDragFling(view: V): Int { return super.getScrollRangeForDragFling(view) } @@ -45,11 +49,21 @@ internal open class AccessorHeaderBehavior(context: Context, attrs: At return super.getTopBottomOffsetForScrollingSibling() } - internal override fun onFlingFinished(parent: CoordinatorLayout?, layout: V) { + internal override fun onFlingFinished(parent: CoordinatorLayout, layout: V) { super.onFlingFinished(parent, layout) } internal override fun canDragView(view: V): Boolean { return super.canDragView(view) } + + internal fun scrollAccessor(coordinatorLayout: CoordinatorLayout, header: V, + dy: Int, minOffset: Int, maxOffset: Int): Int { + return scroll(coordinatorLayout, header, dy, minOffset, maxOffset) + } + + internal fun flingAccessor(coordinatorLayout: CoordinatorLayout, layout: V, minOffset: Int, + maxOffset: Int, velocityY: Float): Boolean { + return fling(coordinatorLayout, layout, minOffset, maxOffset, velocityY) + } } diff --git a/twidere/src/main/kotlin/android/support/design/widget/AnimationUtilsAccessor.kt b/twidere/src/main/kotlin/android/support/design/widget/AnimationUtilsAccessor.kt new file mode 100644 index 000000000..cc41ce0f8 --- /dev/null +++ b/twidere/src/main/kotlin/android/support/design/widget/AnimationUtilsAccessor.kt @@ -0,0 +1,28 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package android.support.design.widget + +import android.animation.TimeInterpolator + +object AnimationUtilsAccessor { + + val DECELERATE_INTERPOLATOR: TimeInterpolator = AnimationUtils.DECELERATE_INTERPOLATOR + +} diff --git a/twidere/src/main/kotlin/android/support/v7/widget/FixedLinearLayoutManager.kt b/twidere/src/main/kotlin/android/support/v7/widget/FixedLinearLayoutManager.kt new file mode 100644 index 000000000..1151d876d --- /dev/null +++ b/twidere/src/main/kotlin/android/support/v7/widget/FixedLinearLayoutManager.kt @@ -0,0 +1,36 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package android.support.v7.widget + +import android.content.Context +import android.view.View + +open class FixedLinearLayoutManager : LinearLayoutManager { + + constructor(context: Context) : super(context) + + constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout) + + internal override fun findOneVisibleChild(fromIndex: Int, toIndex: Int, completelyVisible: Boolean, acceptPartiallyVisible: Boolean): View? { + // XXX Fixed NPE by add a simple check to child view count + return if (childCount < 0) null else super.findOneVisibleChild(fromIndex, toIndex, completelyVisible, acceptPartiallyVisible) + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/ktextension/SyntacticSugar.kt b/twidere/src/main/kotlin/org/mariotaku/ktextension/SyntacticSugar.kt index 9fe2528f6..09fd0e70e 100644 --- a/twidere/src/main/kotlin/org/mariotaku/ktextension/SyntacticSugar.kt +++ b/twidere/src/main/kotlin/org/mariotaku/ktextension/SyntacticSugar.kt @@ -19,4 +19,4 @@ fun LongArray.toStringArray(): Array { return Array(this.size) { idx -> this[idx].toString() } } -fun T.weak(): WeakReference = WeakReference(this) \ No newline at end of file +fun T.toWeak(): WeakReference = WeakReference(this) \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/ktextension/WeakProperty.kt b/twidere/src/main/kotlin/org/mariotaku/ktextension/WeakProperty.kt new file mode 100644 index 000000000..55fa9519e --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/ktextension/WeakProperty.kt @@ -0,0 +1,38 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.ktextension + +import java.lang.ref.WeakReference +import kotlin.reflect.KProperty + +class WeakDelegate { + + private var weakRef: WeakReference? = null + + operator fun getValue(thisRef: Any?, property: KProperty<*>): T? { + return weakRef?.get() + } + + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { + weakRef = if (value != null) WeakReference(value) else null + } +} + +fun weak(): WeakDelegate = WeakDelegate() \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/InvalidAccountAlertActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/InvalidAccountAlertActivity.kt index 7bd153815..778b60dc1 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/InvalidAccountAlertActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/InvalidAccountAlertActivity.kt @@ -12,7 +12,7 @@ import nl.komponents.kovenant.combine.and import nl.komponents.kovenant.task import nl.komponents.kovenant.ui.failUi import nl.komponents.kovenant.ui.successUi -import org.mariotaku.ktextension.weak +import org.mariotaku.ktextension.toWeak import org.mariotaku.twidere.R import org.mariotaku.twidere.activity.iface.IBaseActivity import org.mariotaku.twidere.constant.IntentConstants.EXTRA_INTENT @@ -55,7 +55,7 @@ class InvalidAccountAlertActivity : FragmentActivity(), IBaseActivity diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt index ac3711edb..a77cea3cf 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt @@ -536,7 +536,7 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos private fun saveMediaToContentUri(data: Uri) { val fileInfo = getCurrentCacheFileInfo(viewPager.currentItem) ?: return - val weakThis = weak() + val weakThis = toWeak() (showProgressDialog("save_media_to_progress") and task { val a = weakThis.get() ?: throw InterruptedException() fileInfo.inputStream().use { st -> diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt index a960a682f..af37c982e 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/app/TwidereApplication.kt @@ -35,7 +35,6 @@ import nl.komponents.kovenant.android.startKovenant import nl.komponents.kovenant.android.stopKovenant import nl.komponents.kovenant.task import okhttp3.Dns -import org.mariotaku.commons.logansquare.LoganSquareMapperFinder import org.mariotaku.kpreferences.KPreferences import org.mariotaku.kpreferences.get import org.mariotaku.kpreferences.set @@ -56,7 +55,6 @@ import org.mariotaku.twidere.model.DefaultFeatures import org.mariotaku.twidere.receiver.ConnectivityStateReceiver import org.mariotaku.twidere.service.StreamingService import org.mariotaku.twidere.util.* -import org.mariotaku.twidere.util.concurrent.ConstantFuture import org.mariotaku.twidere.util.content.TwidereSQLiteOpenHelper import org.mariotaku.twidere.util.dagger.GeneralComponent import org.mariotaku.twidere.util.emoji.EmojiOneShortCodeMap @@ -71,8 +69,6 @@ import org.mariotaku.twidere.util.refresh.AutoRefreshController import org.mariotaku.twidere.util.sync.DataSyncProvider import org.mariotaku.twidere.util.sync.SyncController import java.util.* -import java.util.concurrent.Callable -import java.util.concurrent.Future import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -309,16 +305,10 @@ class TwidereApplication : Application(), OnSharedPreferenceChangeListener { Class.forName(AsyncTask::class.java.name) } catch (ignore: ClassNotFoundException) { } - LoganSquareMapperFinder.setDefaultExecutor(object : LoganSquareMapperFinder.FutureExecutor { - override fun submit(callable: Callable): Future { - return ConstantFuture(callable.call()) - } - }) } companion object { - private val KEY_KEYBOARD_SHORTCUT_INITIALIZED = "keyboard_shortcut_initialized" var instance: TwidereApplication? = null private set diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/data/source/CursorObjectTiledDataSource.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/data/source/CursorObjectTiledDataSource.kt index c71363ab9..9370a6c2e 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/data/source/CursorObjectTiledDataSource.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/data/source/CursorObjectTiledDataSource.kt @@ -25,7 +25,7 @@ import android.database.ContentObserver import android.net.Uri import android.os.Handler import android.os.Looper -import org.mariotaku.ktextension.weak +import org.mariotaku.ktextension.toWeak import org.mariotaku.twidere.extension.queryAll import org.mariotaku.twidere.extension.queryCount @@ -40,7 +40,7 @@ class CursorObjectTiledDataSource( ) : TiledDataSource() { init { - val weakThis = weak() + val weakThis = toWeak() val observer = object : ContentObserver(MainHandler) { override fun onChange(selfChange: Boolean) { weakThis.get()?.invalidate() diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsContentRecyclerViewFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsContentRecyclerViewFragment.kt index 0145e9f0a..906e3ce69 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsContentRecyclerViewFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsContentRecyclerViewFragment.kt @@ -36,9 +36,11 @@ import org.mariotaku.twidere.adapter.LoadMoreSupportAdapter import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosition import org.mariotaku.twidere.fragment.iface.RefreshScrollTopInterface -import org.mariotaku.twidere.util.* +import org.mariotaku.twidere.util.ContentScrollHandler +import org.mariotaku.twidere.util.RecyclerViewScrollHandler +import org.mariotaku.twidere.util.ThemeUtils +import org.mariotaku.twidere.util.TwidereColorUtils import org.mariotaku.twidere.view.ExtendedSwipeRefreshLayout -import org.mariotaku.twidere.view.HeaderDrawerLayout import org.mariotaku.twidere.view.iface.IExtendedView /** @@ -46,7 +48,7 @@ import org.mariotaku.twidere.view.iface.IExtendedView */ abstract class AbsContentRecyclerViewFragment, L : RecyclerView.LayoutManager> : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, - HeaderDrawerLayout.DrawerCallback, RefreshScrollTopInterface, IControlBarActivity.ControlBarOffsetListener, + RefreshScrollTopInterface, IControlBarActivity.ControlBarOffsetListener, ContentScrollHandler.ContentListSupport, ControlBarShowHideHelper.ControlBarAnimationListener { lateinit var layoutManager: L @@ -57,7 +59,6 @@ abstract class AbsContentRecyclerViewFragment // Data fields private val systemWindowsInsets = Rect() @@ -84,22 +85,6 @@ abstract class AbsContentRecyclerViewFragment 0) { - tabOutlineAlphaFactor = 1f - ((offset - spaceHeight) / profileContentHeight).coerceIn(0f, 1f) - } else { - tabOutlineAlphaFactor = 1f - } - - actionBarBackground.apply { - this.factor = factor - this.outlineAlphaFactor = tabOutlineAlphaFactor - } - - val currentTabColor = sArgbEvaluator.evaluate(tabOutlineAlphaFactor, - stackedTabColor, cardBackgroundColor) as Int - - val tabBackground = toolbarTabs.background - (tabBackground as ColorDrawable).color = currentTabColor - val tabItemIsDark = ThemeUtils.isLightColor(currentTabColor) - - if (previousTabItemIsDark == 0 || (if (tabItemIsDark) 1 else -1) != previousTabItemIsDark) { - val tabContrastColor = ThemeUtils.getColorDependent(currentTabColor) - toolbarTabs.setIconColor(tabContrastColor) - toolbarTabs.setLabelColor(tabContrastColor) - val theme = Chameleon.getOverrideTheme(activity, activity) - if (theme.isToolbarColored) { - toolbarTabs.setStripColor(tabContrastColor) - } else { - toolbarTabs.setStripColor(ThemeUtils.getOptimalAccentColor(uiColor, tabContrastColor)) - } - toolbarTabs.updateAppearance() - } - previousTabItemIsDark = if (tabItemIsDark) 1 else -1 - - val currentActionBarColor = sArgbEvaluator.evaluate(factor, actionBarShadowColor, - stackedTabColor) as Int - val actionItemIsDark = ThemeUtils.isLightColor(currentActionBarColor) - if (previousActionBarItemIsDark == 0 || (if (actionItemIsDark) 1 else -1) != previousActionBarItemIsDark) { - ThemeUtils.applyToolbarItemColor(activity, toolbar, currentActionBarColor) - } - previousActionBarItemIsDark = if (actionItemIsDark) 1 else -1 - } - override var controlBarOffset: Float get() = 0f set(value) = Unit //Ignore @@ -1649,7 +1596,6 @@ class UserFragment : BaseFragment(), OnClickListener, OnLinkClickListener, companion object { - private val sArgbEvaluator = ArgbEvaluator() private val LOADER_ID_USER = 1 private val LOADER_ID_FRIENDSHIP = 2 diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/filter/FilteredUsersFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/filter/FilteredUsersFragment.kt index 68582d2b5..b0e2d3837 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/filter/FilteredUsersFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/filter/FilteredUsersFragment.kt @@ -215,7 +215,7 @@ class FilteredUsersFragment : BaseFiltersFragment() { } private fun exportToMutedUsers(accountKey: UserKey, items: Array) { - val weakThis = this.weak() + val weakThis = this.toWeak() showProgressDialog("export_to_muted").then { val fragment = weakThis.get() ?: throw InterruptedException() val am = AccountManager.get(fragment.context) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/iface/IToolBarSupportFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/iface/IToolBarSupportFragment.kt index b3263d2e0..7b4aa9915 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/iface/IToolBarSupportFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/iface/IToolBarSupportFragment.kt @@ -3,9 +3,6 @@ package org.mariotaku.twidere.fragment.iface import android.support.v4.app.FragmentActivity import android.support.v7.widget.Toolbar -/** - * Created by mariotaku on 16/3/16. - */ interface IToolBarSupportFragment { val toolbar: Toolbar diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/sync/SyncSettingsFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/sync/SyncSettingsFragment.kt index 7173fa94f..93806a1c9 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/sync/SyncSettingsFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/sync/SyncSettingsFragment.kt @@ -9,7 +9,7 @@ import android.view.MenuItem import com.squareup.otto.Subscribe import nl.komponents.kovenant.combine.and import nl.komponents.kovenant.ui.alwaysUi -import org.mariotaku.ktextension.weak +import org.mariotaku.ktextension.toWeak import org.mariotaku.twidere.R import org.mariotaku.twidere.TwidereConstants.SYNC_PREFERENCES_NAME import org.mariotaku.twidere.constant.dataSyncProviderInfoKey @@ -80,7 +80,7 @@ class SyncSettingsFragment : BasePreferenceFragment() { private fun cleanupAndDisconnect() { val providerInfo = kPreferences[dataSyncProviderInfoKey] ?: return - val weakThis = weak() + val weakThis = toWeak() val task = showProgressDialog("cleanup_sync_cache"). and(syncController.cleanupSyncCache(providerInfo)) task.alwaysUi { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/menu/FavoriteItemProvider.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/menu/FavoriteItemProvider.kt index 2a24ee8ef..b18a938c6 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/menu/FavoriteItemProvider.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/menu/FavoriteItemProvider.kt @@ -28,7 +28,7 @@ import android.support.v4.view.MenuItemCompat import android.support.v7.widget.ActionMenuView import android.view.MenuItem import android.view.View -import org.mariotaku.ktextension.weak +import org.mariotaku.ktextension.toWeak import org.mariotaku.twidere.extension.view.findItemView import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable import org.mariotaku.twidere.graphic.like.LikeAnimationDrawable.Style @@ -75,7 +75,7 @@ class FavoriteItemProvider(context: Context) : ActionProvider(context) { } private class ViewCallback(view: View) : Drawable.Callback { - private val viewRef = view.weak() + private val viewRef = view.toWeak() override fun invalidateDrawable(who: Drawable) { val view = viewRef.get() ?: return diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/HeaderBehavior.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/HeaderBehavior.kt index 3602b554c..5e029f7f0 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/HeaderBehavior.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/HeaderBehavior.kt @@ -24,14 +24,17 @@ import android.content.Context import android.support.design.widget.AccessorHeaderBehavior import android.support.design.widget.CoordinatorLayout import android.support.graphics.drawable.ArgbEvaluator +import android.support.v4.view.ViewCompat +import android.support.v7.widget.RecyclerView import android.util.AttributeSet import android.view.View +import android.view.ViewGroup import kotlinx.android.synthetic.main.fragment_user.view.* import kotlinx.android.synthetic.main.header_user.view.* import org.mariotaku.chameleon.Chameleon import org.mariotaku.chameleon.ChameleonUtils import org.mariotaku.kpreferences.get -import org.mariotaku.twidere.R +import org.mariotaku.ktextension.weak import org.mariotaku.twidere.constant.themeBackgroundAlphaKey import org.mariotaku.twidere.constant.themeBackgroundOptionKey import org.mariotaku.twidere.extension.view.measureChildIgnoringInsets @@ -39,7 +42,8 @@ import org.mariotaku.twidere.graphic.drawable.userprofile.ActionBarDrawable import org.mariotaku.twidere.util.ThemeUtils import org.mariotaku.twidere.util.dagger.DependencyHolder -internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : AccessorHeaderBehavior(context, attrs) { +internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : + AccessorHeaderBehavior(context, attrs) { var offsetDelta: Int = 0 private set @@ -47,48 +51,119 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A private val cardBackgroundColor: Int private var tabItemIsDark: Int = 0 + private var lastNestedScrollingChild by weak() + private var nestedScrollTarget by weak() + init { val preferences = DependencyHolder.get(context).preferences cardBackgroundColor = ThemeUtils.getCardBackgroundColor(context, preferences[themeBackgroundOptionKey], preferences[themeBackgroundAlphaKey]) } - override fun onMeasureChild(parent: CoordinatorLayout, child: View, + override fun onStartNestedScroll(parent: CoordinatorLayout, child: ViewGroup, + directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int): Boolean { + // Return true if we're nested scrolling vertically, and we have scrollable children + // and the scrolling view is big enough to scroll + val started = (nestedScrollAxes and ViewCompat.SCROLL_AXIS_VERTICAL != 0 + && child.hasScrollableChildren + && parent.height - directTargetChild.height <= child.height) + + // A new nested scroll has started so clear out the previous ref + lastNestedScrollingChild = null + + return started + } + + override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: ViewGroup, + target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { + if (dy != 0) { + val min: Int + val max: Int + if (dy < 0) { + // We're scrolling down + min = -child.totalScrollRange + max = min + child.downNestedPreScrollRange + } else { + // We're scrolling up + min = -child.upNestedPreScrollRange + max = 0 + } + if (min != max) { + consumed[1] = scrollAccessor(coordinatorLayout, child, dy, min, max) + } + } + } + + override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: ViewGroup, + target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, + type: Int) { + nestedScrollTarget = target + if (dyUnconsumed < 0) { + // If the scrolling view is scrolling down but not consuming, it's probably be at + // the top of it's content + if (scrollAccessor(coordinatorLayout, child, dyUnconsumed, -child.downNestedScrollRange, + 0) == 0) { + if (target is RecyclerView) { + target.stopScroll() + } else { + ViewCompat.stopNestedScroll(target) + } + } + } + } + + override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: ViewGroup, target: View, + type: Int) { + // Keep a reference to the previous nested scrolling child + lastNestedScrollingChild = target + } + + override fun onMeasureChild(parent: CoordinatorLayout, child: ViewGroup, parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int, heightUsed: Int): Boolean { return parent.measureChildIgnoringInsets(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed) } - override fun onLayoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int): Boolean { - val result = super.onLayoutChild(parent, child, layoutDirection) + override fun onLayoutChild(parent: CoordinatorLayout, child: ViewGroup, layoutDirection: Int): Boolean { + val handled = super.onLayoutChild(parent, child, layoutDirection) updateTabColor(parent, child, topAndBottomOffset) - return result + return handled } - override fun layoutChild(parent: CoordinatorLayout, child: View, layoutDirection: Int) { + override fun layoutChild(parent: CoordinatorLayout, child: ViewGroup, layoutDirection: Int) { child.layout(0, 0, child.measuredWidth, child.measuredHeight) } - override fun getScrollRangeForDragFling(view: View): Int { - val parent = view.parent as CoordinatorLayout - val toolbar = parent.findViewById(R.id.toolbar) - val toolbarTabs = parent.findViewById(R.id.toolbarTabs) - return view.height - toolbar.bottom - toolbarTabs.height + override fun canDragView(view: ViewGroup): Boolean { + // Else we'll use the default behaviour of seeing if it can scroll down + val scrollingView = lastNestedScrollingChild + return if (scrollingView != null) { + // If we have a reference to a scrolling view, check it + scrollingView.isShown && !scrollingView.canScrollVertically(-1) + } else { + // Otherwise we assume that the scrolling view hasn't been scrolled and can drag. + true + } } - override fun getMaxDragOffset(view: View): Int { - val parent = view.parent as CoordinatorLayout - val toolbar = parent.findViewById(R.id.toolbar) - val toolbarTabs = parent.findViewById(R.id.toolbarTabs) - return -(view.height - toolbar.bottom - toolbarTabs.height) + override fun onFlingFinished(parent: CoordinatorLayout, layout: ViewGroup) { + // At the end of a manual fling, check to see if we need to snap to the edge-child } - override fun setHeaderTopBottomOffset(parent: CoordinatorLayout, header: View, newOffset: Int, minOffset: Int, maxOffset: Int): Int { + override fun getMaxDragOffset(view: ViewGroup): Int { + return -view.downNestedScrollRange + } + + override fun getScrollRangeForDragFling(view: ViewGroup): Int { + return view.totalScrollRange + } + + override fun setHeaderTopBottomOffset(parent: CoordinatorLayout, header: ViewGroup, newOffset: Int, minOffset: Int, maxOffset: Int): Int { val curOffset = topBottomOffsetForScrollingSibling var consumed = 0 - if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) { + if (minOffset != 0 && curOffset in minOffset..maxOffset) { // If we have some scrolling range, and we're currently within the min and max // offsets, calculate a new offset val clampedOffset = newOffset.coerceIn(minOffset, maxOffset) @@ -110,6 +185,10 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A return consumed } + override fun getTopBottomOffsetForScrollingSibling(): Int { + return topAndBottomOffset + offsetDelta + } + @SuppressLint("RestrictedApi") private fun updateTabColor(parent: CoordinatorLayout, header: View, offset: Int) { val actionBarBackground = parent.toolbar.background as? ActionBarDrawable ?: return @@ -146,6 +225,5 @@ internal class HeaderBehavior(context: Context, attrs: AttributeSet? = null) : A this.tabItemIsDark = tabItemIsDark } - override fun canDragView(view: View) = true } \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/HeaderLayoutExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/HeaderLayoutExtensions.kt new file mode 100644 index 000000000..8d6f7f771 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/HeaderLayoutExtensions.kt @@ -0,0 +1,49 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.view.behavior.userprofile + +import android.support.design.widget.CoordinatorLayout +import android.view.View +import kotlinx.android.synthetic.main.fragment_user.view.* +import kotlinx.android.synthetic.main.header_user.view.* + +internal val View.hasScrollableChildren: Boolean + get() = totalScrollRange != 0 + +internal val View.totalScrollRange: Int + get() { + val parent = parent as CoordinatorLayout + val toolbar = parent.toolbar + val toolbarTabs = parent.toolbarTabs + return toolbarTabs.top - toolbar.bottom + } + +internal val View.downNestedScrollRange: Int + get() { + val parent = parent as CoordinatorLayout + val toolbar = parent.toolbar + val toolbarTabs = parent.toolbarTabs + return toolbarTabs.top - toolbar.bottom + } + +internal val View.downNestedPreScrollRange: Int + get() = 0 +internal val View.upNestedPreScrollRange: Int + get() = totalScrollRange \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/PagerBehavior.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/PagerBehavior.kt index efe155aee..1b60d7c6e 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/PagerBehavior.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/behavior/userprofile/PagerBehavior.kt @@ -22,20 +22,16 @@ package org.mariotaku.twidere.view.behavior.userprofile import android.content.Context import android.graphics.Rect import android.support.design.widget.AccessorHeaderScrollingViewBehavior -import android.support.design.widget.AppBarLayout import android.support.design.widget.CoordinatorLayout import android.support.v4.view.ViewCompat import android.util.AttributeSet import android.view.View +import kotlinx.android.synthetic.main.header_user.view.* import org.mariotaku.twidere.R import org.mariotaku.twidere.extension.view.measureChildIgnoringInsets internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : AccessorHeaderScrollingViewBehavior(context, attrs) { - override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean { - return dependency.id == R.id.profileHeader - } - override fun onMeasureChild(parent: CoordinatorLayout, child: View, parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int, heightUsed: Int): Boolean { @@ -43,6 +39,10 @@ internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : Ac parentHeightMeasureSpec, heightUsed) } + override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean { + return dependency.id == R.id.profileHeader + } + override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean { offsetChildAsNeeded(parent, child, dependency) @@ -51,7 +51,7 @@ internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : Ac override fun onRequestChildRectangleOnScreen(parent: CoordinatorLayout, child: View, rectangle: Rect, immediate: Boolean): Boolean { - val header = findFirstDependency(parent.getDependencies(child)) ?: return false + val header = parent.getDependencies(child).first() // Offset the rect by the child's left/top rectangle.offset(child.left, child.top) @@ -72,33 +72,30 @@ internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : Ac // Offset the child, pinning it to the bottom the header-dependency, maintaining // any vertical gap and overlap - ViewCompat.offsetTopAndBottom(child, (dependency.bottom - child.top + ViewCompat.offsetTopAndBottom(child, (dependency.contentBottom - child.top + behavior.offsetDelta + verticalLayoutGapAccessor) - getOverlapPixelsForOffsetAccessor(dependency)) } override fun getOverlapRatioForOffset(header: View): Float { -// if (header is AppBarLayout) { -// val abl = header as AppBarLayout? -// val totalScrollRange = abl!!.totalScrollRange -// val preScrollDown = abl.getDownNestedPreScrollRange() -// val offset = getAppBarLayoutOffset(abl) -// -// if (preScrollDown != 0 && totalScrollRange + offset <= preScrollDown) { -// // If we're in a pre-scroll down. Don't use the offset at all. -// return 0f -// } else { -// val availScrollRange = totalScrollRange - preScrollDown -// if (availScrollRange != 0) { -// // Else we'll use a interpolated ratio of the overlap, depending on offset -// return 1f + offset / availScrollRange.toFloat() -// } -// } -// } + val totalScrollRange = header.totalScrollRange + val preScrollDown = header.downNestedPreScrollRange + val offset = getAppBarLayoutOffset(header) + + if (preScrollDown != 0 && totalScrollRange + offset <= preScrollDown) { + // If we're in a pre-scroll down. Don't use the offset at all. + return 0f + } else { + val availScrollRange = totalScrollRange - preScrollDown + if (availScrollRange != 0) { + // Else we'll use a interpolated ratio of the overlap, depending on offset + return 1f + offset / availScrollRange.toFloat() + } + } return 0f } - private fun getAppBarLayoutOffset(abl: AppBarLayout): Int { + private fun getAppBarLayoutOffset(abl: View): Int { val lp = abl.layoutParams as CoordinatorLayout.LayoutParams val behavior = lp.behavior as? HeaderBehavior ?: return 0 return behavior.topBottomOffsetForScrollingSibling @@ -109,7 +106,10 @@ internal class PagerBehavior(context: Context, attrs: AttributeSet? = null) : Ac } override fun getScrollRange(v: View): Int { - return (v as? AppBarLayout)?.totalScrollRange ?: super.getScrollRange(v) + if (v.id == R.id.profileHeader) return v.totalScrollRange + return super.getScrollRange(v) } + private val View.contentBottom + get() = top + toolbarTabs.bottom } \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/userprofile/UserProfileCoordinatorLayout.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/userprofile/UserProfileCoordinatorLayout.kt new file mode 100644 index 000000000..9bb827553 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/userprofile/UserProfileCoordinatorLayout.kt @@ -0,0 +1,30 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.view.userprofile + +import android.content.Context +import android.support.design.widget.CoordinatorLayout +import android.util.AttributeSet +import org.mariotaku.chameleon.ChameleonView + +class UserProfileCoordinatorLayout(context: Context, attrs: AttributeSet?) : + CoordinatorLayout(context, attrs), ChameleonView.StatusBarThemeable { + override fun isStatusBarColorHandled() = true +} diff --git a/twidere/src/main/res/layout/fragment_user.xml b/twidere/src/main/res/layout/fragment_user.xml index d560cbe94..f50c7431e 100644 --- a/twidere/src/main/res/layout/fragment_user.xml +++ b/twidere/src/main/res/layout/fragment_user.xml @@ -18,7 +18,7 @@ ~ along with this program. If not, see . --> - @@ -128,7 +129,6 @@ android:layout_width="match_parent" android:layout_height="?actionBarSize" android:layout_alignParentTop="true" - android:elevation="@dimen/toolbar_elevation" app:layout_behavior="org.mariotaku.twidere.view.behavior.userprofile.ToolbarBehavior" app:popupTheme="?actionBarPopupTheme" tools:background="@drawable/shadow_bottom" @@ -144,4 +144,4 @@ android:layout_height="wrap_content" android:background="?android:windowContentOverlay"/> - \ No newline at end of file + \ No newline at end of file diff --git a/twidere/src/main/res/layout/header_user.xml b/twidere/src/main/res/layout/header_user.xml index 0025da662..18abf8c14 100644 --- a/twidere/src/main/res/layout/header_user.xml +++ b/twidere/src/main/res/layout/header_user.xml @@ -23,11 +23,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/headerUserProfile" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:clipChildren="false" - android:clipToPadding="false"> + android:layout_height="wrap_content"> + tools:layoutManager="android.support.v7.widget.LinearLayoutManager" + tools:listitem="@layout/layout_tab_item" + tools:orientation="horizontal"/> - - - -