added drag sort and swipe remove for status media, improved situations mentioned in #249

This commit is contained in:
Mariotaku Lee 2015-11-25 15:33:18 +08:00
parent 95c976a741
commit 0859d66d8f
23 changed files with 406 additions and 1034 deletions

View File

@ -7,7 +7,7 @@ android:
- tools
# The BuildTools version used by your project
- build-tools-23.0.1
- build-tools-23.0.2
# The SDK version used to compile your project
- android-23

View File

@ -32,7 +32,7 @@ subprojects {
if (project.hasProperty('android')) {
android {
compileSdkVersion 23
buildToolsVersion '23.0.1'
buildToolsVersion '23.0.2'
lintOptions {
abortOnError false

View File

@ -2,6 +2,7 @@ package org.mariotaku.twidere.model;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.bluelinelabs.logansquare.LoganSquare;
@ -28,6 +29,8 @@ public class ParcelableMediaUpdate implements Parcelable {
}
};
@SuppressWarnings("NullableProblems")
@NonNull
@JsonField(name = "uri")
public String uri;
@JsonField(name = "type")
@ -41,7 +44,7 @@ public class ParcelableMediaUpdate implements Parcelable {
type = in.readInt();
}
public ParcelableMediaUpdate(final String uri, final int type) {
public ParcelableMediaUpdate(@NonNull final String uri, final int type) {
this.uri = uri;
this.type = type;
}
@ -62,6 +65,25 @@ public class ParcelableMediaUpdate implements Parcelable {
dest.writeInt(type);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParcelableMediaUpdate that = (ParcelableMediaUpdate) o;
if (type != that.type) return false;
return uri.equals(that.uri);
}
@Override
public int hashCode() {
int result = uri.hashCode();
result = 31 * result + type;
return result;
}
@Deprecated
public static ParcelableMediaUpdate[] fromJSONString(final String json) {
if (TextUtils.isEmpty(json)) return null;

View File

@ -1,23 +0,0 @@
/*
* Copyright (C) 2013 Jacek Marchwicki <jacek.marchwicki@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.dynamicgridview;
public interface DraggableAdapter {
void reorderElements(int position, int newPosition);
void swapElements(int position, int newPosition);
}

View File

@ -1,122 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.dynamicgridview;
import android.content.Context;
import org.mariotaku.twidere.adapter.ArrayAdapter;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
public class DraggableArrayAdapter<T> extends ArrayAdapter<T> implements DraggableAdapter {
final int INVALID_ID = -1;
private final HashMap<T, Integer> mIdMap = new HashMap<>();
public DraggableArrayAdapter(final Context context, final int layoutRes) {
this(context, layoutRes, null);
}
public DraggableArrayAdapter(final Context context, final int layoutRes, final Collection<? extends T> collection) {
super(context, layoutRes, collection);
rebuildIdMap();
}
@Override
public void add(final T item) {
super.add(item);
rebuildIdMap();
}
@Override
public void addAll(final Collection<? extends T> collection) {
super.addAll(collection);
rebuildIdMap();
}
@Override
public void clear() {
super.clear();
rebuildIdMap();
}
@Override
public long getItemId(final int position) {
if (position < 0 || position >= mIdMap.size()) return INVALID_ID;
final T item = getItem(position);
return mIdMap.get(item);
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public void removeAt(final int position) {
super.removeAt(position);
rebuildIdMap();
}
@Override
public void removeAll(final Collection<? extends T> collection) {
super.removeAll(collection);
rebuildIdMap();
}
@Override
public void reorderElements(final int position, final int newPosition) {
final List<T> objects = getObjects();
T previous = objects.get(position);
final int iterator = newPosition < position ? 1 : -1;
final int afterPosition = position + iterator;
for (int cellPosition = newPosition; cellPosition != afterPosition; cellPosition += iterator) {
final T tmp = objects.get(cellPosition);
objects.set(cellPosition, previous);
previous = tmp;
}
notifyDataSetChanged();
}
@Override
public void sort(final Comparator<? super T> comparator) {
super.sort(comparator);
rebuildIdMap();
}
@Override
public void swapElements(final int position, final int newPosition) {
final List<T> objects = getObjects();
final T temp = objects.get(position);
objects.set(position, objects.get(newPosition));
objects.set(newPosition, temp);
notifyDataSetChanged();
}
private void rebuildIdMap() {
mIdMap.clear();
final List<T> objects = getObjects();
for (int i = 0, j = objects.size(); i < j; ++i) {
mIdMap.put(objects.get(i), i);
}
}
}

View File

@ -1,648 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
* Copyright (C) 2013 Jacek Marchwicki <jacek.marchwicki@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mariotaku.dynamicgridview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListAdapter;
import java.util.HashMap;
/**
* The dynamic gridview is an extension of gridview that supports cell dragging
* and swapping.
*
* This layout is in charge of positioning the hover cell in the correct
* location on the screen in response to user touch events. It uses the position
* of the hover cell to determine when two cells should be swapped. If two cells
* should be swapped, all the corresponding data set and layout changes are
* handled here.
*
* If no cell is selected, all the touch events are passed down to the gridview
* and behave normally. If one of the items in the gridview experiences a long
* press event, the contents of its current visible state are captured as a
* bitmap and its visibility is set to INVISIBLE. A hover cell is then created
* and added to this layout as an overlaying BitmapDrawable above the gridview.
* Once the hover cell is translated some distance to signify an item swap, a
* data set change accompanied by animation takes place. When the user releases
* the hover cell, it animates into its corresponding position in the gridview.
*
* When the hover cell is either above or below the bounds of the gridview, this
* gridview also scrolls on its own so as to reveal additional content.
*/
public class DynamicGridView extends GridView {
private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15;
private final int MOVE_DURATION = 150;
private final int LINE_THICKNESS = 15;
private int mLastEventX = -1;
private int mLastEventY = -1;
private int mDownY = -1;
private int mDownX = -1;
private final int mTotalOffsetX = 0;
private int mTotalOffsetY = 0;
private boolean mCellIsMobile = false;
private boolean mIsMobileScrolling = false;
private int mSmoothScrollAmountAtEdge = 0;
private final int INVALID_ID = -1;
private long mMobileItemId = INVALID_ID;
private BitmapDrawable mHoverCell;
private Rect mHoverCellCurrentBounds;
private Rect mHoverCellOriginalBounds;
private final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private boolean mIsWaitingForScrollFinish = false;
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
/**
* Listens for long clicks on any items in the listview. When a cell has
* been selected, the hover cell is created and set up.
*/
private final OnItemLongClickListener mOnItemLongClickListener = new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(final AdapterView<?> arg0, final View arg1, final int pos, final long id) {
mTotalOffsetY = 0;
final int position = pointToPosition(mDownX, mDownY);
final int itemNum = position - getFirstVisiblePosition();
final View selectedView = getChildAt(itemNum);
mMobileItemId = getAdapter().getItemId(position);
mHoverCell = getAndAddHoverView(selectedView);
selectedView.setVisibility(INVISIBLE);
mCellIsMobile = true;
return true;
}
};
private final HashMap<Long, Integer> mItemIdTops = new HashMap<>();
private final HashMap<Long, Integer> mItemIdLefts = new HashMap<>();
/**
* This TypeEvaluator is used to animate the BitmapDrawable back to its
* final location when the user lifts his finger by modifying the
* BitmapDrawable's bounds.
*/
private final static TypeEvaluator<Rect> sBoundEvaluator = new TypeEvaluator<Rect>() {
@Override
public Rect evaluate(final float fraction, final Rect startValue, final Rect endValue) {
return new Rect(interpolate(startValue.left, endValue.left, fraction), interpolate(startValue.top,
endValue.top, fraction), interpolate(startValue.right, endValue.right, fraction), interpolate(
startValue.bottom, endValue.bottom, fraction));
}
public int interpolate(final int start, final int end, final float fraction) {
return (int) (start + fraction * (end - start));
}
};
/**
* This scroll listener is added to the gridview in order to handle cell
* swapping when the cell is either at the top or bottom edge of the
* gridview. If the hover cell is at either edge of the gridview, the
* gridview will begin scrolling. As scrolling takes place, the gridview
* continuously checks if new cells became visible and determines whether
* they are potential candidates for a cell swap.
*/
private final OnScrollListener mScrollListener = new OnScrollListener() {
private int mPreviousFirstVisibleItem = -1;
private int mPreviousVisibleItemCount = -1;
private int mCurrentFirstVisibleItem;
private int mCurrentVisibleItemCount;
private int mCurrentScrollState;
/**
* Determines if the gridview scrolled up enough to reveal a new cell at
* the top of the list. If so, then the appropriate parameters are
* updated.
*/
public void checkAndHandleFirstVisibleCellChange() {
if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
handleCellSwitch();
}
}
}
/**
* Determines if the gridview scrolled down enough to reveal a new cell
* at the bottom of the list. If so, then the appropriate parameters are
* updated.
*/
public void checkAndHandleLastVisibleCellChange() {
final int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
final int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
if (currentLastVisibleItem != previousLastVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
handleCellSwitch();
}
}
}
@Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) {
mCurrentFirstVisibleItem = firstVisibleItem;
mCurrentVisibleItemCount = visibleItemCount;
mPreviousFirstVisibleItem = mPreviousFirstVisibleItem == -1 ? mCurrentFirstVisibleItem
: mPreviousFirstVisibleItem;
mPreviousVisibleItemCount = mPreviousVisibleItemCount == -1 ? mCurrentVisibleItemCount
: mPreviousVisibleItemCount;
checkAndHandleFirstVisibleCellChange();
checkAndHandleLastVisibleCellChange();
mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
mPreviousVisibleItemCount = mCurrentVisibleItemCount;
}
@Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
mCurrentScrollState = scrollState;
mScrollState = scrollState;
isScrollCompleted();
}
/**
* This method is in charge of invoking 1 of 2 actions. Firstly, if the
* gridview is in a state of scrolling invoked by the hover cell being
* outside the bounds of the gridview, then this scrolling event is
* continued. Secondly, if the hover cell has already been released,
* this invokes the animation for the hover cell to return to its
* correct position after the gridview has entered an idle scroll state.
*/
private void isScrollCompleted() {
if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
if (mCellIsMobile && mIsMobileScrolling) {
handleMobileCellScroll();
} else if (mIsWaitingForScrollFinish) {
touchEventsEnded();
}
}
}
};
public DynamicGridView(final Context context) {
super(context);
init(context);
}
public DynamicGridView(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DynamicGridView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
init(context);
}
/** Retrieves the position in the list corresponding to itemID */
public int getPositionForID(final long itemID) {
final View v = getViewForID(itemID);
if (v == null)
return -1;
else
return getPositionForView(v);
}
/** Retrieves the view in the list corresponding to itemID */
public View getViewForID(final long itemID) {
final int firstVisiblePosition = getFirstVisiblePosition();
final ListAdapter adapter = getAdapter();
for (int i = 0; i < getChildCount(); i++) {
final View v = getChildAt(i);
final int position = firstVisiblePosition + i;
final long id = adapter.getItemId(position);
if (id == itemID) return v;
}
return null;
}
public View getViewForPosition(final int position) {
if (position < 0) return null;
if (position >= getCount()) return null;
final ListAdapter adapter = getAdapter();
final long itemId = adapter.getItemId(position);
return getViewForID(itemId);
}
/**
* This method is in charge of determining if the hover cell is above or
* below the bounds of the gridview. If so, the gridview does an appropriate
* upward or downward smooth scroll so as to reveal new items.
*/
public boolean handleMobileCellScroll(final Rect r) {
final int offset = computeVerticalScrollOffset();
final int height = getHeight();
final int extent = computeVerticalScrollExtent();
final int range = computeVerticalScrollRange();
final int hoverViewTop = r.top;
final int hoverHeight = r.height();
if (hoverViewTop <= 0 && offset > 0) {
smoothScrollBy(-mSmoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewTop + hoverHeight >= height && offset + extent < range) {
smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
public void init(final Context context) {
setOnItemLongClickListener(mOnItemLongClickListener);
setOnScrollListener(mScrollListener);
final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mSmoothScrollAmountAtEdge = (int) (SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density);
}
@Override
public boolean onTouchEvent(@NonNull final MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mDownX = (int) event.getX();
mDownY = (int) event.getY();
mActivePointerId = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_POINTER_ID) {
break;
}
int pointerIndex = event.findPointerIndex(mActivePointerId);
mLastEventX = (int) event.getX(pointerIndex);
mLastEventY = (int) event.getY(pointerIndex);
final int deltaX = mLastEventX - mDownX;
final int deltaY = mLastEventY - mDownY;
if (mCellIsMobile) {
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left + deltaX + mTotalOffsetX,
mHoverCellOriginalBounds.top + deltaY + mTotalOffsetY);
mHoverCell.setBounds(mHoverCellCurrentBounds);
invalidate();
handleCellSwitch();
mIsMobileScrolling = false;
handleMobileCellScroll();
return false;
}
break;
case MotionEvent.ACTION_UP:
touchEventsEnded();
break;
case MotionEvent.ACTION_CANCEL:
touchEventsCancelled();
break;
case MotionEvent.ACTION_POINTER_UP:
/*
* If a multitouch event took place and the original touch
* dictating the movement of the hover cell has ended, then the
* dragging event ends and the hover cell is animated to its
* corresponding position in the gridview.
*/
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
touchEventsEnded();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public void setAdapter(final ListAdapter adapter) {
if (isInEditMode()) return;
if (!(adapter instanceof DraggableAdapter))
throw new IllegalArgumentException("Adapter have to implement DraggableAdapter");
super.setAdapter(adapter);
}
/**
* dispatchDraw gets invoked when all the child views are about to be drawn.
* By overriding this method, the hover cell (BitmapDrawable) can be drawn
* over the gridview's items whenever the gridview is redrawn.
*/
@Override
protected void dispatchDraw(@NonNull final Canvas canvas) {
super.dispatchDraw(canvas);
if (mHoverCell != null) {
mHoverCell.draw(canvas);
}
}
/**
* Creates the hover cell with the appropriate bitmap and of appropriate
* size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
* single time an invalidate call is made.
*/
private BitmapDrawable getAndAddHoverView(final View v) {
final int w = v.getWidth();
final int h = v.getHeight();
final int top = v.getTop();
final int left = v.getLeft();
final Bitmap b = getBitmapWithBorder(v);
final BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
drawable.setBounds(mHoverCellCurrentBounds);
return drawable;
}
/** Returns a bitmap showing a screenshot of the view passed in. */
private Bitmap getBitmapFromView(final View v) {
final Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
v.draw(canvas);
return bitmap;
}
/** Draws a black border over the screenshot of the view passed in. */
private Bitmap getBitmapWithBorder(final View v) {
final Bitmap bitmap = getBitmapFromView(v);
final Canvas can = new Canvas(bitmap);
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(LINE_THICKNESS);
paint.setColor(Color.BLACK);
can.drawBitmap(bitmap, 0, 0, null);
can.drawRect(rect, paint);
return bitmap;
}
/**
* This method determines whether the hover cell has been shifted far enough
* to invoke a cell swap. If so, then the respective cell swap candidate is
* determined and the data set is changed. Upon posting a notification of
* the data set change, a layout is invoked to place the cells in the right
* place. Using a ViewTreeObserver and a corresponding OnPreDrawListener, we
* can offset the cell being swapped to where it previously was and then
* animate it to its new position.
*/
private void handleCellSwitch() {
final int deltaY = mLastEventY - mDownY;
final int deltaX = mLastEventX - mDownX;
final int deltaYTotal = mHoverCellOriginalBounds.top + mTotalOffsetY + deltaY;
final int deltaXTotal = mHoverCellOriginalBounds.left + mTotalOffsetX + deltaX;
final int numColumns = getNumColumns();
final int position = getPositionForID(mMobileItemId);
final int abovePosition = position - numColumns;
final int belowPosition = position + numColumns;
final int toLeftPosition = position - 1;
final int toRightPosition = position + 1;
final View aboveView = getViewForPosition(abovePosition);
final View belowView = getViewForPosition(belowPosition);
View toLeftView = getViewForPosition(toLeftPosition);
View toRightView = getViewForPosition(toRightPosition);
final View mobileView = getViewForID(mMobileItemId);
if (toRightView != null && mobileView.getLeft() > toRightView.getLeft()) {
// mobile view is far right
toRightView = null;
}
if (toLeftView != null && mobileView.getLeft() < toLeftView.getLeft()) {
// mobile view is far left
toLeftView = null;
}
final boolean isBelow = belowView != null && deltaYTotal > belowView.getTop();
final boolean isAbove = aboveView != null && deltaYTotal < aboveView.getTop();
final boolean isToRight = toRightView != null && deltaXTotal > toRightView.getLeft();
final boolean isToLeft = toLeftView != null && deltaXTotal < toLeftView.getLeft();
int newPosition;
if (isBelow) {
newPosition = belowPosition;
} else if (isAbove) {
newPosition = abovePosition;
} else if (isToLeft) {
newPosition = toLeftPosition;
} else if (isToRight) {
newPosition = toRightPosition;
} else {
newPosition = position;
}
if (newPosition == position) return;
final ListAdapter adapter = getAdapter();
final int fromPosition = Math.min(newPosition, position);
final int toPosition = Math.max(newPosition, position);
for (int cellPosition = fromPosition; cellPosition <= toPosition; cellPosition++) {
getViewForPosition(cellPosition).setVisibility(View.VISIBLE);
}
mItemIdLefts.clear();
mItemIdTops.clear();
final int firstVisiblePosition = getFirstVisiblePosition();
final int childCount = getChildCount();
for (int childAt = 0; childAt < childCount; childAt++) {
final View child = getChildAt(childAt);
assert child != null;
final int pos = firstVisiblePosition + childAt;
final long itemId = adapter.getItemId(pos);
mItemIdLefts.put(itemId, child.getLeft());
mItemIdTops.put(itemId, child.getTop());
}
final ViewTreeObserver observer = getViewTreeObserver();
assert observer != null;
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
final int firstVisiblePosition = getFirstVisiblePosition();
final int childCount = getChildCount();
for (int childAt = 0; childAt < childCount; childAt++) {
final View child = getChildAt(childAt);
assert child != null;
final int pos = firstVisiblePosition + childAt;
final long itemId = adapter.getItemId(pos);
if (itemId == mMobileItemId) {
child.setVisibility(View.GONE);
continue;
}
final Integer oldLeft = mItemIdLefts.get(itemId);
final Integer oldTop = mItemIdTops.get(itemId);
if (oldLeft == null) {
continue;
}
mItemIdLefts.put(itemId, child.getLeft());
mItemIdTops.put(itemId, child.getTop());
final int newLeft = child.getLeft();
final int newTop = child.getTop();
final int deltaX = oldLeft - newLeft;
final int deltaY = oldTop - newTop;
if (deltaX == 0 && deltaY == 0) {
continue;
}
child.setTranslationX(deltaX);
child.setTranslationY(deltaY);
final AnimatorSet animator = new AnimatorSet();
animator.playTogether(ObjectAnimator.ofFloat(child, View.TRANSLATION_X, 0),
ObjectAnimator.ofFloat(child, View.TRANSLATION_Y, 0));
animator.setDuration(MOVE_DURATION).start();
}
return true;
}
});
((DraggableAdapter) adapter).reorderElements(position, newPosition);
}
/**
* Determines whether this gridview is in a scrolling state invoked by the
* fact that the hover cell is out of the bounds of the gridview;
*/
private void handleMobileCellScroll() {
mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
}
/**
* Resets all the appropriate fields to a default state.
*/
private void touchEventsCancelled() {
final View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile) {
mMobileItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
invalidate();
}
mCellIsMobile = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
}
/**
* Resets all the appropriate fields to a default state while also animating
* the hover cell back to its correct location.
*/
private void touchEventsEnded() {
final View mobileView = getViewForID(mMobileItemId);
if (mCellIsMobile || mIsWaitingForScrollFinish) {
mCellIsMobile = false;
mIsWaitingForScrollFinish = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_POINTER_ID;
// If the autoscroller has not completed scrolling, we need to wait
// for it to
// finish in order to determine the final location of where the
// hover cell
// should be animated to.
if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
mIsWaitingForScrollFinish = true;
return;
}
mHoverCellCurrentBounds.offsetTo(mobileView.getLeft(), mobileView.getTop());
final ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds", sBoundEvaluator,
mHoverCellCurrentBounds);
hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(final ValueAnimator valueAnimator) {
invalidate();
}
});
hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(final Animator animation) {
mMobileItemId = INVALID_ID;
mobileView.setVisibility(VISIBLE);
mHoverCell = null;
setEnabled(true);
invalidate();
}
@Override
public void onAnimationStart(final Animator animation) {
setEnabled(false);
}
});
hoverViewAnimator.start();
} else {
touchEventsCancelled();
}
}
}

View File

@ -33,6 +33,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.location.Criteria;
@ -61,6 +62,7 @@ import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ItemDecoration;
import android.support.v7.widget.RecyclerView.State;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.text.Editable;
import android.text.Spannable;
import android.text.Spanned;
@ -84,7 +86,6 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@ -95,8 +96,8 @@ import com.github.johnpersano.supertoasts.SuperToast.OnDismissListener;
import com.nostra13.universalimageloader.utils.IoUtils;
import com.twitter.Extractor;
import org.mariotaku.dynamicgridview.DraggableArrayAdapter;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.ArrayRecyclerAdapter;
import org.mariotaku.twidere.adapter.BaseRecyclerViewAdapter;
import org.mariotaku.twidere.fragment.support.BaseSupportDialogFragment;
import org.mariotaku.twidere.fragment.support.SupportProgressDialogFragment;
@ -119,7 +120,6 @@ import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.EditTextEnterHandler;
import org.mariotaku.twidere.util.EditTextEnterHandler.EnterListener;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.MathUtils;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MenuUtils;
import org.mariotaku.twidere.util.ParseUtils;
@ -134,6 +134,7 @@ import org.mariotaku.twidere.view.BadgeView;
import org.mariotaku.twidere.view.ComposeEditText;
import org.mariotaku.twidere.view.ShapedImageView;
import org.mariotaku.twidere.view.StatusTextCountView;
import org.mariotaku.twidere.view.helper.SimpleItemTouchHelperCallback;
import java.io.File;
import java.io.FileOutputStream;
@ -167,7 +168,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
private SupportMenuInflater mMenuInflater;
// Views
private GridView mMediaPreviewGrid;
private RecyclerView mAttachedMediaPreview;
private ActionMenuView mMenuBar;
private ComposeEditText mEditText;
private View mSendView;
@ -199,6 +200,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
private SetProgressVisibleRunnable mSetProgressVisibleRunnable;
private boolean mFragmentResumed;
private int mKeyMetaState;
private ItemTouchHelper mItemTouchHelper;
@Override
public int getThemeColor() {
@ -524,7 +526,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
public void onContentChanged() {
super.onContentChanged();
mEditText = (ComposeEditText) findViewById(R.id.edit_text);
mMediaPreviewGrid = (GridView) findViewById(R.id.media_thumbnail_preview);
mAttachedMediaPreview = (RecyclerView) findViewById(R.id.attached_media_preview);
mMenuBar = (ActionMenuView) findViewById(R.id.menu_bar);
mSendView = findViewById(R.id.send);
mSendTextCountView = (StatusTextCountView) mSendView.findViewById(R.id.status_text_count);
@ -549,7 +551,6 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
public void removeAllMedia(final List<ParcelableMediaUpdate> list) {
mMediaPreviewAdapter.removeAll(list);
updateMediaPreview();
}
public void saveToDrafts() {
@ -621,8 +622,26 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
mAccountSelector.setAdapter(mAccountsAdapter);
mAccountsAdapter.setAccounts(ParcelableCredentials.getCredentialsArray(this, false, false));
mMediaPreviewAdapter = new MediaPreviewAdapter(this);
mMediaPreviewGrid.setAdapter(mMediaPreviewAdapter);
mMediaPreviewAdapter = new MediaPreviewAdapter(this, new SimpleItemTouchHelperCallback.OnStartDragListener() {
@Override
public void onStartDrag(ViewHolder viewHolder) {
mItemTouchHelper.startDrag(viewHolder);
}
});
mItemTouchHelper = new ItemTouchHelper(new AttachedMediaItemTouchHelperCallback(mMediaPreviewAdapter));
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mAttachedMediaPreview.setLayoutManager(layoutManager);
mAttachedMediaPreview.setAdapter(mMediaPreviewAdapter);
mItemTouchHelper.attachToRecyclerView(mAttachedMediaPreview);
final int previewGridSpacing = getResources().getDimensionPixelSize(R.dimen.element_spacing_small);
mAttachedMediaPreview.addItemDecoration(new ItemDecoration() {
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
outRect.left = outRect.right = previewGridSpacing;
}
});
final Intent intent = getIntent();
@ -679,10 +698,11 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
}
setMenu();
updateLocationState();
updateMediaPreview();
notifyAccountSelectionChanged();
mTextChanged = false;
updateAttachedMediaView();
}
@Override
@ -794,17 +814,23 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
private void addMedia(final ParcelableMediaUpdate media) {
mMediaPreviewAdapter.add(media);
updateMediaPreview();
updateAttachedMediaView();
}
private void addMedia(final List<ParcelableMediaUpdate> media) {
mMediaPreviewAdapter.addAll(media);
updateMediaPreview();
updateAttachedMediaView();
}
private void clearMedia() {
mMediaPreviewAdapter.clear();
updateMediaPreview();
updateAttachedMediaView();
}
private void updateAttachedMediaView() {
mAttachedMediaPreview.setVisibility(hasMedia() ? View.VISIBLE : View.GONE);
setMenu();
}
private Uri createTempImageUri() {
@ -924,7 +950,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
if (user == null || user.id <= 0) return false;
final String my_screen_name = Utils.getAccountScreenName(this, user.account_id);
if (TextUtils.isEmpty(my_screen_name)) return false;
mEditText.setText("@" + user.screen_name + " ");
mEditText.setText(String.format("@%s ", user.screen_name));
final int selection_end = mEditText.length();
mEditText.setSelection(selection_end);
mAccountsAdapter.setSelectedAccountIds(user.account_id);
@ -985,7 +1011,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
}
private boolean hasMedia() {
return !mMediaPreviewAdapter.isEmpty();
return mMediaPreviewAdapter.getItemCount() > 0;
}
private boolean isQuote() {
@ -1076,7 +1102,6 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
MenuUtils.setMenuItemAvailability(menu, R.id.view, hasInReplyTo);
MenuUtils.setMenuItemAvailability(menu, R.id.media_menu, hasMedia || hasInReplyTo);
MenuUtils.setMenuItemAvailability(menu, R.id.toggle_sensitive, hasMedia);
MenuUtils.setMenuItemAvailability(menu, R.id.edit_media, hasMedia);
MenuUtils.setMenuItemAvailability(menu, R.id.link_to_quoted_status, isQuote());
MenuUtils.setMenuItemAvailability(menu, R.id.schedule, isScheduleSupported());
@ -1133,30 +1158,6 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
}
}
private static class SetProgressVisibleRunnable implements Runnable {
private final ComposeActivity activity;
private final boolean visible;
SetProgressVisibleRunnable(ComposeActivity activity, boolean visible) {
this.activity = activity;
this.visible = visible;
}
@Override
public void run() {
final FragmentManager fm = activity.getSupportFragmentManager();
final Fragment f = fm.findFragmentByTag(DISCARD_STATUS_DIALOG_FRAGMENT_TAG);
if (!visible && f instanceof DialogFragment) {
((DialogFragment) f).dismiss();
} else if (visible) {
SupportProgressDialogFragment df = new SupportProgressDialogFragment();
df.show(fm, DISCARD_STATUS_DIALOG_FRAGMENT_TAG);
df.setCancelable(false);
}
}
}
private void setRecentLocation(ParcelableLocation location) {
if (location != null) {
mLocationText.setText(location.getHumanReadableString(3));
@ -1238,13 +1239,6 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
}
}
private void updateMediaPreview() {
final int count = mMediaPreviewAdapter.getCount();
final Resources res = getResources();
final int maxColumns = res.getInteger(R.integer.grid_column_image_preview);
mMediaPreviewGrid.setNumColumns(MathUtils.clamp(count, maxColumns, 1));
}
private void updateStatus() {
if (isFinishing()) return;
final boolean hasMedia = hasMedia();
@ -1303,6 +1297,30 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
mSendTextCountView.setTextCount(validatedCount);
}
private static class SetProgressVisibleRunnable implements Runnable {
private final ComposeActivity activity;
private final boolean visible;
SetProgressVisibleRunnable(ComposeActivity activity, boolean visible) {
this.activity = activity;
this.visible = visible;
}
@Override
public void run() {
final FragmentManager fm = activity.getSupportFragmentManager();
final Fragment f = fm.findFragmentByTag(DISCARD_STATUS_DIALOG_FRAGMENT_TAG);
if (!visible && f instanceof DialogFragment) {
((DialogFragment) f).dismiss();
} else if (visible) {
SupportProgressDialogFragment df = new SupportProgressDialogFragment();
df.show(fm, DISCARD_STATUS_DIALOG_FRAGMENT_TAG);
df.setCancelable(false);
}
}
}
static class AccountIconViewHolder extends ViewHolder implements OnClickListener {
private final AccountIconsAdapter adapter;
@ -1540,7 +1558,6 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
if (mMedia == null) return false;
try {
for (final ParcelableMediaUpdate media : mMedia) {
if (media.uri == null) continue;
final Uri uri = Uri.parse(media.uri);
if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
final File file = new File(uri.getPath());
@ -1582,7 +1599,6 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
@Override
protected Object doInBackground(final Object... params) {
for (final ParcelableMediaUpdate media : mActivity.getMediaList()) {
if (media.uri == null) continue;
final Uri uri = Uri.parse(media.uri);
if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
final File file = new File(uri.getPath());
@ -1606,28 +1622,77 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
}
}
private static class MediaPreviewAdapter extends DraggableArrayAdapter<ParcelableMediaUpdate> {
public static class MediaPreviewAdapter extends ArrayRecyclerAdapter<ParcelableMediaUpdate, MediaPreviewViewHolder>
implements SimpleItemTouchHelperCallback.ItemTouchHelperAdapter {
private final MediaLoaderWrapper mImageLoader;
private final LayoutInflater mInflater;
public MediaPreviewAdapter(final ComposeActivity activity) {
super(activity, R.layout.grid_item_media_editor);
mImageLoader = activity.mImageLoader;
private final SimpleItemTouchHelperCallback.OnStartDragListener mDragStartListener;
public MediaPreviewAdapter(final ComposeActivity activity, SimpleItemTouchHelperCallback.OnStartDragListener dragStartListener) {
super(activity);
mInflater = LayoutInflater.from(activity);
mDragStartListener = dragStartListener;
}
public void onStartDrag(ViewHolder viewHolder) {
mDragStartListener.onStartDrag(viewHolder);
}
public List<ParcelableMediaUpdate> getAsList() {
return Collections.unmodifiableList(getObjects());
return Collections.unmodifiableList(mData);
}
@Override
public void onBindViewHolder(MediaPreviewViewHolder holder, int position, ParcelableMediaUpdate item) {
final ParcelableMediaUpdate media = getItem(position);
holder.displayMedia(this, media);
}
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final View view = super.getView(position, convertView, parent);
final ParcelableMediaUpdate media = getItem(position);
final ImageView image = (ImageView) view.findViewById(R.id.image);
mImageLoader.displayPreviewImage(media.uri, image);
return view;
public MediaPreviewViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = mInflater.inflate(R.layout.grid_item_media_editor, parent, false);
return new MediaPreviewViewHolder(this, view);
}
@Override
public void onItemDismiss(int position) {
mData.remove(position);
notifyItemRemoved(position);
((ComposeActivity) getContext()).updateAttachedMediaView();
}
@Override
public boolean onItemMove(int fromPosition, int toPosition) {
Collections.swap(mData, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
return true;
}
}
static class MediaPreviewViewHolder extends ViewHolder implements OnLongClickListener {
private final ImageView image;
private final MediaPreviewAdapter adapter;
public MediaPreviewViewHolder(MediaPreviewAdapter adapter, View itemView) {
super(itemView);
this.adapter = adapter;
itemView.setOnLongClickListener(this);
image = (ImageView) itemView.findViewById(R.id.image);
}
public void displayMedia(MediaPreviewAdapter adapter, ParcelableMediaUpdate media) {
adapter.getMediaLoader().displayPreviewImage(media.uri, image);
}
@Override
public boolean onLongClick(View v) {
adapter.onStartDrag(this);
return true;
}
}
@ -1683,4 +1748,47 @@ public class ComposeActivity extends ThemedFragmentActivity implements LocationL
}
}
public static class AttachedMediaItemTouchHelperCallback extends SimpleItemTouchHelperCallback {
public static final float ALPHA_FULL = 1.0f;
public AttachedMediaItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
super(adapter);
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return true;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
// Set movement flags based on the layout manager
final int dragFlags = ItemTouchHelper.START | ItemTouchHelper.END;
final int swipeFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
// Fade out the view as it is swiped out of the parent's bounds
final float alpha = ALPHA_FULL - Math.abs(dY) / (float) viewHolder.itemView.getHeight();
viewHolder.itemView.setAlpha(alpha);
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
} else {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
@Override
public void clearView(RecyclerView recyclerView, ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setAlpha(ALPHA_FULL);
}
}
}

View File

@ -21,7 +21,6 @@ package org.mariotaku.twidere.adapter;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity;
import android.support.v4.util.Pair;
import android.support.v7.widget.CardView;
@ -39,13 +38,10 @@ import org.mariotaku.twidere.fragment.support.UserFragment;
import org.mariotaku.twidere.model.ParcelableActivity;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MediaLoadingHandler;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.TwidereLinkify;
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener;
import org.mariotaku.twidere.util.UserColorNameManager;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.view.holder.ActivityTitleSummaryViewHolder;
import org.mariotaku.twidere.view.holder.GapViewHolder;
@ -99,12 +95,6 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
@Override
public abstract void setData(Data data);
@NonNull
@Override
public MediaLoaderWrapper getMediaLoader() {
return mMediaLoader;
}
@Override
public MediaLoadingHandler getMediaLoadingHandler() {
return mLoadingHandler;
@ -120,12 +110,6 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
return mStatusAdapterDelegate.getMediaPreviewStyle();
}
@NonNull
@Override
public AsyncTwitterWrapper getTwitterWrapper() {
return mTwitterWrapper;
}
@Override
public float getTextSize() {
return mStatusAdapterDelegate.getTextSize();
@ -335,12 +319,6 @@ public abstract class AbsActivitiesAdapter<Data> extends LoadMoreSupportAdapter<
}
@NonNull
@Override
public UserColorNameManager getUserColorNameManager() {
return mUserColorNameManager;
}
public void setListener(ActivityAdapterListener listener) {
mActivityAdapterListener = listener;
}

View File

@ -90,11 +90,6 @@ public abstract class AbsStatusesAdapter<D> extends LoadMoreSupportAdapter<ViewH
return mShowAccountsColor;
}
@NonNull
@Override
public final MediaLoaderWrapper getMediaLoader() {
return mMediaLoader;
}
@Override
public final MediaLoadingHandler getMediaLoadingHandler() {
@ -111,18 +106,6 @@ public abstract class AbsStatusesAdapter<D> extends LoadMoreSupportAdapter<ViewH
return mMediaPreviewStyle;
}
@NonNull
@Override
public final AsyncTwitterWrapper getTwitterWrapper() {
return mTwitterWrapper;
}
@NonNull
@Override
public UserColorNameManager getUserColorNameManager() {
return mUserColorNameManager;
}
@Override
public final float getTextSize() {
return mTextSize;

View File

@ -74,17 +74,6 @@ public abstract class AbsUserListsAdapter<D> extends LoadMoreSupportAdapter<View
return mTextSize;
}
@NonNull
@Override
public AsyncTwitterWrapper getTwitterWrapper() {
return mTwitterWrapper;
}
@NonNull
@Override
public UserColorNameManager getUserColorNameManager() {
return mUserColorNameManager;
}
@Override
public boolean isProfileImageEnabled() {
@ -177,12 +166,6 @@ public abstract class AbsUserListsAdapter<D> extends LoadMoreSupportAdapter<View
return false;
}
@NonNull
@Override
public MediaLoaderWrapper getMediaLoader() {
return mMediaLoader;
}
protected abstract void bindUserList(UserListViewHolder holder, int position);

View File

@ -20,7 +20,6 @@
package org.mariotaku.twidere.adapter;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
@ -30,10 +29,7 @@ import android.view.ViewGroup;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.iface.IUsersAdapter;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.UserColorNameManager;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder;
import org.mariotaku.twidere.view.holder.UserViewHolder;
@ -71,18 +67,6 @@ public abstract class AbsUsersAdapter<D> extends LoadMoreSupportAdapter<ViewHold
return mTextSize;
}
@NonNull
@Override
public AsyncTwitterWrapper getTwitterWrapper() {
return mTwitterWrapper;
}
@NonNull
@Override
public UserColorNameManager getUserColorNameManager() {
return mUserColorNameManager;
}
@Override
public boolean isProfileImageEnabled() {
return mDisplayProfileImage;
@ -169,12 +153,6 @@ public abstract class AbsUsersAdapter<D> extends LoadMoreSupportAdapter<ViewHold
return false;
}
@NonNull
@Override
public MediaLoaderWrapper getMediaLoader() {
return mMediaLoader;
}
protected abstract void bindUser(UserViewHolder holder, int position);

View File

@ -1,6 +1,6 @@
package org.mariotaku.twidere.adapter;
import android.support.v7.widget.RecyclerView.Adapter;
import android.content.Context;
import android.support.v7.widget.RecyclerView.ViewHolder;
import java.util.ArrayList;
@ -12,10 +12,14 @@ import java.util.List;
/**
* Created by mariotaku on 14/10/27.
*/
public abstract class ArrayRecyclerAdapter<T, H extends ViewHolder> extends Adapter<H> {
public abstract class ArrayRecyclerAdapter<T, H extends ViewHolder> extends BaseRecyclerViewAdapter<H> {
protected final ArrayList<T> mData = new ArrayList<>();
public ArrayRecyclerAdapter(Context context) {
super(context);
}
@Override
public final void onBindViewHolder(H holder, int position) {
onBindViewHolder(holder, position, getItem(position));

View File

@ -52,6 +52,7 @@ public abstract class BaseRecyclerViewAdapter<VH extends RecyclerView.ViewHolder
protected UserColorNameManager mUserColorNameManager;
@Inject
protected SharedPreferencesWrapper mPreferences;
public BaseRecyclerViewAdapter(Context context) {
mContext = context;
//noinspection unchecked
@ -65,4 +66,28 @@ public abstract class BaseRecyclerViewAdapter<VH extends RecyclerView.ViewHolder
public final Context getContext() {
return mContext;
}
public final SharedPreferencesWrapper getPreferences() {
return mPreferences;
}
public final UserColorNameManager getUserColorNameManager() {
return mUserColorNameManager;
}
public final MultiSelectManager getMultiSelectManager() {
return mMultiSelectManager;
}
public final MediaLoaderWrapper getMediaLoader() {
return mMediaLoader;
}
public final ReadStateManager getReadStateManager() {
return mReadStateManager;
}
public final AsyncTwitterWrapper getTwitterWrapper() {
return mTwitterWrapper;
}
}

View File

@ -34,7 +34,6 @@ import org.mariotaku.twidere.adapter.iface.IDirectMessagesAdapter;
import org.mariotaku.twidere.model.ParcelableDirectMessage;
import org.mariotaku.twidere.model.ParcelableDirectMessage.CursorIndices;
import org.mariotaku.twidere.util.DirectMessageOnLinkClickHandler;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.MediaLoadingHandler;
import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.TwidereLinkify;
@ -75,11 +74,6 @@ public class MessageConversationAdapter extends BaseRecyclerViewAdapter<ViewHold
mOutgoingMessageColor = ThemeUtils.getCardBackgroundColor(context, ThemeUtils.getThemeBackgroundOption(context), ThemeUtils.getUserThemeBackgroundAlpha(context));
}
@Override
public MediaLoaderWrapper getMediaLoader() {
return mMediaLoader;
}
public MediaLoadingHandler getMediaLoadingHandler() {
return mMediaLoadingHandler;
}

View File

@ -23,7 +23,6 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
@ -35,10 +34,7 @@ import org.mariotaku.twidere.R;
import org.mariotaku.twidere.adapter.iface.IContentCardAdapter;
import org.mariotaku.twidere.model.StringLongPair;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
import org.mariotaku.twidere.util.ReadStateManager.OnReadStateChangeListener;
import org.mariotaku.twidere.util.UserColorNameManager;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder;
import org.mariotaku.twidere.view.holder.MessageEntryViewHolder;
@ -87,12 +83,6 @@ public class MessageEntriesAdapter extends LoadMoreSupportAdapter<ViewHolder> im
return mTextSize;
}
@NonNull
@Override
public AsyncTwitterWrapper getTwitterWrapper() {
return mTwitterWrapper;
}
@Override
public boolean isProfileImageEnabled() {
return mDisplayProfileImage;
@ -104,18 +94,6 @@ public class MessageEntriesAdapter extends LoadMoreSupportAdapter<ViewHolder> im
return new DirectMessageEntry(c);
}
@NonNull
@Override
public MediaLoaderWrapper getMediaLoader() {
return mMediaLoader;
}
@NonNull
@Override
public UserColorNameManager getUserColorNameManager() {
return mUserColorNameManager;
}
@Override
public void onClick(final View view) {
// if (mMultiSelectManager.isActive()) return;

View File

@ -1262,23 +1262,11 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
return mTextSize;
}
@NonNull
@Override
public AsyncTwitterWrapper getTwitterWrapper() {
return mFragment.mTwitterWrapper;
}
@Override
public boolean isProfileImageEnabled() {
return mDisplayProfileImage;
}
@NonNull
@Override
public MediaLoaderWrapper getMediaLoader() {
return mMediaLoader;
}
public StatusFragment getFragment() {
return mFragment;
}
@ -1390,12 +1378,6 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac
return mMediaLoadingHandler;
}
@NonNull
@Override
public UserColorNameManager getUserColorNameManager() {
return mUserColorNameManager;
}
public ParcelableStatus getStatus() {
return mStatus;
}

View File

@ -24,6 +24,7 @@ import android.support.v7.widget.RecyclerView;
import org.mariotaku.twidere.activity.BasePreferenceActivity;
import org.mariotaku.twidere.activity.BaseThemedActivity;
import org.mariotaku.twidere.activity.support.BaseAppCompatActivity;
import org.mariotaku.twidere.activity.support.ComposeActivity;
import org.mariotaku.twidere.activity.support.ThemedFragmentActivity;
import org.mariotaku.twidere.adapter.AccountsAdapter;
import org.mariotaku.twidere.adapter.AccountsSpinnerAdapter;
@ -106,4 +107,5 @@ public interface GeneralComponent {
void inject(BaseFiltersFragment.FilteredUsersFragment.FilterUsersListAdapter object);
void inject(AccountsDashboardFragment.OptionItemsAdapter object);
}

View File

@ -43,28 +43,13 @@ public class InetAddressUtils {
}
private InetAddressUtils() {
throw new AssertionError("Trying to instantiate this class");
}
/**
* Checks whether the parameter is a valid IPv4 address
*
* @param input the address string to check for validity
* @return true if the input parameter is a valid IPv4 address
* @param input IP address in string
* @return type corresponding to &lt;sys/socket.h&gt;
*/
public static boolean isIPv4Address(final String input) {
return getInetAddressType(input) == 2; // AF_INET4
}
/**
* Checks whether the parameter is a valid IPv6 address (including compressed).
*
* @param input the address string to check for validity
* @return true if the input parameter is a valid standard or compressed IPv6 address
*/
public static boolean isIPv6Address(final String input) {
return getInetAddressType(input) == 10; // AF_INET6
}
public native static int getInetAddressType(final String input);
public native static InetAddress getResolvedIPAddress(@Nullable final String host, @NonNull final String address);

View File

@ -79,12 +79,6 @@ public class TwidereDns implements Constants, Dns {
@NonNull
private InetAddress[] resolveInternal(String originalHost, String host) throws IOException {
try {
Log.d(LOGTAG, "Test Resolved " + Arrays.toString(mResolver.resolve("localhost")));
Log.d(LOGTAG, "Test Resolved " + Arrays.toString(mResolver.resolve("ip6-localhost")));
} catch (IOException e) {
// Ignore
}
if (isValidIpAddress(host)) return fromAddressString(originalHost, host);
// First, I'll try to load address cached.
final InetAddress[] cachedHostAddr = mHostCache.get(host);
@ -197,7 +191,7 @@ public class TwidereDns implements Constants, Dns {
}
private static boolean isValidIpAddress(final String address) {
return InetAddressUtils.isIPv4Address(address) || InetAddressUtils.isIPv6Address(address);
return InetAddressUtils.getInetAddressType(address) != 0;
}
@Override

View File

@ -0,0 +1,154 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.view.helper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
/**
* An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
* swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/>
* </br/>
* Expects the <code>RecyclerView.Adapter</code> to listen for {@link
* ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> to implement
* {@link ItemTouchHelperViewHolder}.
*
* @author Paul Burke (ipaulpro)
*/
public abstract class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
protected final ItemTouchHelperAdapter mAdapter;
public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
mAdapter = adapter;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
if (source.getItemViewType() != target.getItemViewType()) {
return false;
}
// Notify the adapter of the move
mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// Notify the adapter of the dismissal
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
// We only want the active item to change
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (viewHolder instanceof ItemTouchHelperViewHolder) {
// Let the view holder know that this item is being moved or dragged
ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemSelected();
}
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (viewHolder instanceof ItemTouchHelperViewHolder) {
// Tell the view holder it's time to restore the idle state
ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
itemViewHolder.onItemClear();
}
}
/**
* Interface to notify an item ViewHolder of relevant callbacks from {@link
* ItemTouchHelper.Callback}.
*
* @author Paul Burke (ipaulpro)
*/
public interface ItemTouchHelperViewHolder {
/**
* Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
* Implementations should update the item view to indicate it's active state.
*/
void onItemSelected();
/**
* Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item
* state should be cleared.
*/
void onItemClear();
}
/**
* Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
*
* @author Paul Burke (ipaulpro)
*/
public interface ItemTouchHelperAdapter {
/**
* Called when an item has been dragged far enough to trigger a move. This is called every time
* an item is shifted, and <strong>not</strong> at the end of a "drop" event.<br/>
* <br/>
* Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after
* adjusting the underlying data to reflect this move.
*
* @param fromPosition The start position of the moved item.
* @param toPosition Then resolved position of the moved item.
* @return True if the item was moved to the new adapter position.
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
* @see RecyclerView.ViewHolder#getAdapterPosition()
*/
boolean onItemMove(int fromPosition, int toPosition);
/**
* Called when an item has been dismissed by a swipe.<br/>
* <br/>
* Implementations should call {@link RecyclerView.Adapter#notifyItemRemoved(int)} after
* adjusting the underlying data to reflect this removal.
*
* @param position The position of the item dismissed.
* @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
* @see RecyclerView.ViewHolder#getAdapterPosition()
*/
void onItemDismiss(int position);
}
/**
* Listener for manual initiation of a drag.
*/
public interface OnStartDragListener {
/**
* Called when a view is requesting a start of a drag.
*
* @param viewHolder The holder of the view to drag.
*/
void onStartDrag(RecyclerView.ViewHolder viewHolder);
}
}

View File

@ -31,21 +31,6 @@
android:layout_height="0dp"
android:layout_weight="1">
<org.mariotaku.dynamicgridview.DynamicGridView
android:id="@+id/media_thumbnail_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="@+id/edit_text_container"
android:layout_alignEnd="@+id/edit_text_container"
android:layout_alignLeft="@+id/edit_text_container"
android:layout_alignRight="@+id/edit_text_container"
android:layout_alignStart="@+id/edit_text_container"
android:layout_alignTop="@+id/edit_text_container"
android:alpha="0.2"
android:numColumns="@integer/grid_column_image_preview"
android:stretchMode="columnWidth"
tools:listitem="@layout/gallery_item_image_preview" />
<LinearLayout
android:id="@+id/edit_text_container"
android:layout_width="match_parent"
@ -66,6 +51,14 @@
android:scrollbars="vertical"
android:singleLine="false" />
<android.support.v7.widget.RecyclerView
android:id="@+id/attached_media_preview"
android:layout_width="match_parent"
android:layout_height="128dp"
android:layout_weight="0"
tools:listitem="@layout/grid_item_media_editor" />
<LinearLayout
android:id="@+id/location_container"
android:layout_width="match_parent"

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Twidere - Twitter client for Android
~
~ Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
@ -18,9 +17,16 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.mariotaku.twidere.view.SquareHighlightImageView
android:id="@+id/image"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"/>
<org.mariotaku.twidere.view.SquareFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent"
tools:layout_height="128dp">
<org.mariotaku.twidere.view.HighlightImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
</org.mariotaku.twidere.view.SquareFrameLayout>

View File

@ -31,10 +31,6 @@
android:id="@+id/add_image_sub_item"
android:icon="@drawable/ic_action_gallery"
android:title="@string/add_image" />
<item
android:id="@id/edit_media"
android:icon="@drawable/ic_action_edit"
android:title="@string/edit_media" />
<item
android:id="@id/toggle_sensitive"
android:checkable="true"