From 8b0111823315837b227b79560c25c7925872cb53 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 13:13:41 +0200 Subject: [PATCH 01/14] Remove local copy of com.mobeta.android.dslv in favour of importing it from maven --- core/library/build.gradle | 21 - core/library/lint-baseline.xml | 59 - core/library/src/main/AndroidManifest.xml | 6 - .../android/dslv/DragSortController.java | 465 --- .../android/dslv/DragSortCursorAdapter.java | 241 -- .../mobeta/android/dslv/DragSortItemView.java | 100 - .../dslv/DragSortItemViewCheckable.java | 55 - .../mobeta/android/dslv/DragSortListView.java | 3042 ----------------- .../dslv/ResourceDragSortCursorAdapter.java | 133 - .../dslv/SimpleDragSortCursorAdapter.java | 422 --- .../android/dslv/SimpleFloatViewManager.java | 89 - .../src/main/res/values/dslv_attrs.xml | 30 - dependencies.gradle | 2 + settings.gradle | 1 - ultrasonic/build.gradle | 2 +- .../src/main/res/layout/current_playlist.xml | 3 +- 16 files changed, 4 insertions(+), 4667 deletions(-) delete mode 100644 core/library/build.gradle delete mode 100644 core/library/lint-baseline.xml delete mode 100644 core/library/src/main/AndroidManifest.xml delete mode 100644 core/library/src/main/java/com/mobeta/android/dslv/DragSortController.java delete mode 100644 core/library/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java delete mode 100644 core/library/src/main/java/com/mobeta/android/dslv/DragSortItemView.java delete mode 100644 core/library/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java delete mode 100644 core/library/src/main/java/com/mobeta/android/dslv/DragSortListView.java delete mode 100644 core/library/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java delete mode 100644 core/library/src/main/java/com/mobeta/android/dslv/SimpleDragSortCursorAdapter.java delete mode 100644 core/library/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java delete mode 100644 core/library/src/main/res/values/dslv_attrs.xml diff --git a/core/library/build.gradle b/core/library/build.gradle deleted file mode 100644 index 0a7ec71e..00000000 --- a/core/library/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -apply from: bootstrap.androidModule -apply plugin: 'com.android.library' - -android { - lintOptions { - baselineFile file("lint-baseline.xml") - abortOnError true - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } -} - -dependencies { - api androidSupport.support - implementation other.timber -} diff --git a/core/library/lint-baseline.xml b/core/library/lint-baseline.xml deleted file mode 100644 index 76081cd7..00000000 --- a/core/library/lint-baseline.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/library/src/main/AndroidManifest.xml b/core/library/src/main/AndroidManifest.xml deleted file mode 100644 index 7a731338..00000000 --- a/core/library/src/main/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/core/library/src/main/java/com/mobeta/android/dslv/DragSortController.java b/core/library/src/main/java/com/mobeta/android/dslv/DragSortController.java deleted file mode 100644 index 6a644e0f..00000000 --- a/core/library/src/main/java/com/mobeta/android/dslv/DragSortController.java +++ /dev/null @@ -1,465 +0,0 @@ -package com.mobeta.android.dslv; - -import android.graphics.Point; -import android.view.GestureDetector; -import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.widget.AdapterView; - -/** - * Class that starts and stops item drags on a {@link DragSortListView} - * based on touch gestures. This class also inherits from - * {@link SimpleFloatViewManager}, which provides basic float View - * creation. - * - * An instance of this class is meant to be passed to the methods - * {@link DragSortListView#setTouchListener()} and - * {@link DragSortListView#setFloatViewManager()} of your - * {@link DragSortListView} instance. - */ -public class DragSortController extends SimpleFloatViewManager implements View.OnTouchListener, GestureDetector.OnGestureListener { - - /** - * Drag init mode enum. - */ - public static final int ON_DOWN = 0; - public static final int ON_DRAG = 1; - public static final int ON_LONG_PRESS = 2; - - private int mDragInitMode = ON_DOWN; - - private boolean mSortEnabled = true; - - /** - * Remove mode enum. - */ - public static final int CLICK_REMOVE = 0; - public static final int FLING_REMOVE = 1; - - /** - * The current remove mode. - */ - private int mRemoveMode; - - private boolean mRemoveEnabled = false; - private boolean mIsRemoving = false; - - private GestureDetector mDetector; - - private GestureDetector mFlingRemoveDetector; - - private int mTouchSlop; - - public static final int MISS = -1; - - private int mHitPos = MISS; - private int mFlingHitPos = MISS; - - private int mClickRemoveHitPos = MISS; - - private int[] mTempLoc = new int[2]; - - private int mItemX; - private int mItemY; - - private int mCurrX; - private int mCurrY; - - private boolean mDragging = false; - - private float mFlingSpeed = 500f; - - private int mDragHandleId; - - private int mClickRemoveId; - - private int mFlingHandleId; - private boolean mCanDrag; - - private DragSortListView mDslv; - private int mPositionX; - - /** - * Calls {@link #DragSortController(DragSortListView, int)} with a - * 0 drag handle id, FLING_RIGHT_REMOVE remove mode, - * and ON_DOWN drag init. By default, sorting is enabled, and - * removal is disabled. - * - * @param dslv The DSLV instance - */ - public DragSortController(DragSortListView dslv) { - this(dslv, 0, ON_DOWN, FLING_REMOVE); - } - - public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode) { - this(dslv, dragHandleId, dragInitMode, removeMode, 0); - } - - public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, int removeMode, int clickRemoveId) { - this(dslv, dragHandleId, dragInitMode, removeMode, clickRemoveId, 0); - } - - /** - * By default, sorting is enabled, and removal is disabled. - * - * @param dslv The DSLV instance - * @param dragHandleId The resource id of the View that represents - * the drag handle in a list item. - */ - public DragSortController(DragSortListView dslv, int dragHandleId, int dragInitMode, - int removeMode, int clickRemoveId, int flingHandleId) { - super(dslv); - mDslv = dslv; - mDetector = new GestureDetector(dslv.getContext(), this); - mFlingRemoveDetector = new GestureDetector(dslv.getContext(), mFlingRemoveListener); - mFlingRemoveDetector.setIsLongpressEnabled(false); - mTouchSlop = ViewConfiguration.get(dslv.getContext()).getScaledTouchSlop(); - mDragHandleId = dragHandleId; - mClickRemoveId = clickRemoveId; - mFlingHandleId = flingHandleId; - setRemoveMode(removeMode); - setDragInitMode(dragInitMode); - } - - - public int getDragInitMode() { - return mDragInitMode; - } - - /** - * Set how a drag is initiated. Needs to be one of - * {@link ON_DOWN}, {@link ON_DRAG}, or {@link ON_LONG_PRESS}. - * - * @param mode The drag init mode. - */ - public void setDragInitMode(int mode) { - mDragInitMode = mode; - } - - /** - * Enable/Disable list item sorting. Disabling is useful if only item - * removal is desired. Prevents drags in the vertical direction. - * - * @param enabled Set true to enable list - * item sorting. - */ - public void setSortEnabled(boolean enabled) { - mSortEnabled = enabled; - } - - public boolean isSortEnabled() { - return mSortEnabled; - } - - /** - * One of {@link CLICK_REMOVE}, {@link FLING_RIGHT_REMOVE}, - * {@link FLING_LEFT_REMOVE}, - * {@link SLIDE_RIGHT_REMOVE}, or {@link SLIDE_LEFT_REMOVE}. - */ - public void setRemoveMode(int mode) { - mRemoveMode = mode; - } - - public int getRemoveMode() { - return mRemoveMode; - } - - /** - * Enable/Disable item removal without affecting remove mode. - */ - public void setRemoveEnabled(boolean enabled) { - mRemoveEnabled = enabled; - } - - public boolean isRemoveEnabled() { - return mRemoveEnabled; - } - - /** - * Set the resource id for the View that represents the drag - * handle in a list item. - * - * @param id An android resource id. - */ - public void setDragHandleId(int id) { - mDragHandleId = id; - } - - /** - * Set the resource id for the View that represents the fling - * handle in a list item. - * - * @param id An android resource id. - */ - public void setFlingHandleId(int id) { - mFlingHandleId = id; - } - - /** - * Set the resource id for the View that represents click - * removal button. - * - * @param id An android resource id. - */ - public void setClickRemoveId(int id) { - mClickRemoveId = id; - } - - /** - * Sets flags to restrict certain motions of the floating View - * based on DragSortController settings (such as remove mode). - * Starts the drag on the DragSortListView. - * - * @param position The list item position (includes headers). - * @param deltaX Touch x-coord minus left edge of floating View. - * @param deltaY Touch y-coord minus top edge of floating View. - * - * @return True if drag started, false otherwise. - */ - public boolean startDrag(int position, int deltaX, int deltaY) { - - int dragFlags = 0; - if (mSortEnabled && !mIsRemoving) { - dragFlags |= DragSortListView.DRAG_POS_Y | DragSortListView.DRAG_NEG_Y; - } - if (mRemoveEnabled && mIsRemoving) { - dragFlags |= DragSortListView.DRAG_POS_X; - dragFlags |= DragSortListView.DRAG_NEG_X; - } - - mDragging = mDslv.startDrag(position - mDslv.getHeaderViewsCount(), dragFlags, deltaX, - deltaY); - return mDragging; - } - - @Override - public boolean onTouch(View v, MotionEvent ev) { - if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) { - return false; - } - - mDetector.onTouchEvent(ev); - if (mRemoveEnabled && mDragging && mRemoveMode == FLING_REMOVE) { - mFlingRemoveDetector.onTouchEvent(ev); - } - - int action = ev.getAction() & MotionEvent.ACTION_MASK; - switch (action) { - case MotionEvent.ACTION_DOWN: - mCurrX = (int) ev.getX(); - mCurrY = (int) ev.getY(); - break; - case MotionEvent.ACTION_UP: - if (mRemoveEnabled && mIsRemoving) { - int x = mPositionX >= 0 ? mPositionX : -mPositionX; - int removePoint = mDslv.getWidth() / 2; - if (x > removePoint) { - mDslv.stopDragWithVelocity(true, 0); - } - } - case MotionEvent.ACTION_CANCEL: - mIsRemoving = false; - mDragging = false; - break; - } - - return false; - } - - /** - * Overrides to provide fading when slide removal is enabled. - */ - @Override - public void onDragFloatView(View floatView, Point position, Point touch) { - - if (mRemoveEnabled && mIsRemoving) { - mPositionX = position.x; - } - } - - /** - * Get the position to start dragging based on the ACTION_DOWN - * MotionEvent. This function simply calls - * {@link #dragHandleHitPosition(MotionEvent)}. Override - * to change drag handle behavior; - * this function is called internally when an ACTION_DOWN - * event is detected. - * - * @param ev The ACTION_DOWN MotionEvent. - * - * @return The list position to drag if a drag-init gesture is - * detected; MISS if unsuccessful. - */ - public int startDragPosition(MotionEvent ev) { - return dragHandleHitPosition(ev); - } - - public int startFlingPosition(MotionEvent ev) { - return mRemoveMode == FLING_REMOVE ? flingHandleHitPosition(ev) : MISS; - } - - /** - * Checks for the touch of an item's drag handle (specified by - * {@link #setDragHandleId(int)}), and returns that item's position - * if a drag handle touch was detected. - * - * @param ev The ACTION_DOWN MotionEvent. - - * @return The list position of the item whose drag handle was - * touched; MISS if unsuccessful. - */ - public int dragHandleHitPosition(MotionEvent ev) { - return viewIdHitPosition(ev, mDragHandleId); - } - - public int flingHandleHitPosition(MotionEvent ev) { - return viewIdHitPosition(ev, mFlingHandleId); - } - - public int viewIdHitPosition(MotionEvent ev, int id) { - final int x = (int) ev.getX(); - final int y = (int) ev.getY(); - - int touchPos = mDslv.pointToPosition(x, y); // includes headers/footers - - final int numHeaders = mDslv.getHeaderViewsCount(); - final int numFooters = mDslv.getFooterViewsCount(); - final int count = mDslv.getCount(); - - // We're only interested if the touch was on an - // item that's not a header or footer. - if (touchPos != AdapterView.INVALID_POSITION && touchPos >= numHeaders - && touchPos < (count - numFooters)) { - final View item = mDslv.getChildAt(touchPos - mDslv.getFirstVisiblePosition()); - final int rawX = (int) ev.getRawX(); - final int rawY = (int) ev.getRawY(); - - View dragBox = id == 0 ? item : (View) item.findViewById(id); - if (dragBox != null) { - dragBox.getLocationOnScreen(mTempLoc); - - if (rawX > mTempLoc[0] && rawY > mTempLoc[1] && - rawX < mTempLoc[0] + dragBox.getWidth() && - rawY < mTempLoc[1] + dragBox.getHeight()) { - - mItemX = item.getLeft(); - mItemY = item.getTop(); - - return touchPos; - } - } - } - - return MISS; - } - - @Override - public boolean onDown(MotionEvent ev) { - if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) { - mClickRemoveHitPos = viewIdHitPosition(ev, mClickRemoveId); - } - - mHitPos = startDragPosition(ev); - if (mHitPos != MISS && mDragInitMode == ON_DOWN) { - startDrag(mHitPos, (int) ev.getX() - mItemX, (int) ev.getY() - mItemY); - } - - mIsRemoving = false; - mCanDrag = true; - mPositionX = 0; - mFlingHitPos = startFlingPosition(ev); - - return true; - } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - - final int x1 = (int) e1.getX(); - final int y1 = (int) e1.getY(); - final int x2 = (int) e2.getX(); - final int y2 = (int) e2.getY(); - final int deltaX = x2 - mItemX; - final int deltaY = y2 - mItemY; - - if (mCanDrag && !mDragging && (mHitPos != MISS || mFlingHitPos != MISS)) { - if (mHitPos != MISS) { - if (mDragInitMode == ON_DRAG && Math.abs(y2 - y1) > mTouchSlop && mSortEnabled) { - startDrag(mHitPos, deltaX, deltaY); - } - else if (mDragInitMode != ON_DOWN && Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) - { - mIsRemoving = true; - startDrag(mFlingHitPos, deltaX, deltaY); - } - } else if (mFlingHitPos != MISS) { - if (Math.abs(x2 - x1) > mTouchSlop && mRemoveEnabled) { - mIsRemoving = true; - startDrag(mFlingHitPos, deltaX, deltaY); - } else if (Math.abs(y2 - y1) > mTouchSlop) { - mCanDrag = false; // if started to scroll the list then - // don't allow sorting nor fling-removing - } - } - } - // return whatever - return false; - } - - @Override - public void onLongPress(MotionEvent e) { - if (mHitPos != MISS && mDragInitMode == ON_LONG_PRESS) { - mDslv.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - startDrag(mHitPos, mCurrX - mItemX, mCurrY - mItemY); - } - } - - // complete the OnGestureListener interface - @Override - public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - return false; - } - - // complete the OnGestureListener interface - @Override - public boolean onSingleTapUp(MotionEvent ev) { - if (mRemoveEnabled && mRemoveMode == CLICK_REMOVE) { - if (mClickRemoveHitPos != MISS) { - mDslv.removeItem(mClickRemoveHitPos - mDslv.getHeaderViewsCount()); - } - } - return true; - } - - // complete the OnGestureListener interface - @Override - public void onShowPress(MotionEvent ev) { - // do nothing - } - - private GestureDetector.OnGestureListener mFlingRemoveListener = - new GestureDetector.SimpleOnGestureListener() { - @Override - public final boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, - float velocityY) { - if (mRemoveEnabled && mIsRemoving) { - int w = mDslv.getWidth(); - int minPos = w / 5; - if (velocityX > mFlingSpeed) { - if (mPositionX > -minPos) { - mDslv.stopDragWithVelocity(true, velocityX); - } - } else if (velocityX < -mFlingSpeed) { - if (mPositionX < minPos) { - mDslv.stopDragWithVelocity(true, velocityX); - } - } - mIsRemoving = false; - } - return false; - } - }; - -} diff --git a/core/library/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java b/core/library/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java deleted file mode 100644 index 9e14059b..00000000 --- a/core/library/src/main/java/com/mobeta/android/dslv/DragSortCursorAdapter.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.mobeta.android.dslv; - -import java.util.ArrayList; - -import android.content.Context; -import android.database.Cursor; -import android.util.SparseIntArray; -import android.view.View; -import android.view.ViewGroup; -import androidx.cursoradapter.widget.CursorAdapter; - - -/** - * A subclass of {@link android.widget.CursorAdapter} that provides - * reordering of the elements in the Cursor based on completed - * drag-sort operations. The reordering is a simple mapping of - * list positions into Cursor positions (the Cursor is unchanged). - * To persist changes made by drag-sorts, one can retrieve the - * mapping with the {@link #getCursorPositions()} method, which - * returns the reordered list of Cursor positions. - * - * An instance of this class is passed - * to {@link DragSortListView#setAdapter(ListAdapter)} and, since - * this class implements the {@link DragSortListView.DragSortListener} - * interface, it is automatically set as the DragSortListener for - * the DragSortListView instance. - */ -public abstract class DragSortCursorAdapter extends CursorAdapter implements DragSortListView.DragSortListener { - - public static final int REMOVED = -1; - - /** - * Key is ListView position, value is Cursor position - */ - private SparseIntArray mListMapping = new SparseIntArray(); - - private ArrayList mRemovedCursorPositions = new ArrayList(); - - public DragSortCursorAdapter(Context context, Cursor c) { - super(context, c); - } - - public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) { - super(context, c, autoRequery); - } - - public DragSortCursorAdapter(Context context, Cursor c, int flags) { - super(context, c, flags); - } - - /** - * Swaps Cursor and clears list-Cursor mapping. - * - * @see android.widget.CursorAdapter#swapCursor(android.database.Cursor) - */ - @Override - public Cursor swapCursor(Cursor newCursor) { - Cursor old = super.swapCursor(newCursor); - resetMappings(); - return old; - } - - /** - * Changes Cursor and clears list-Cursor mapping. - * - * @see android.widget.CursorAdapter#changeCursor(android.database.Cursor) - */ - @Override - public void changeCursor(Cursor cursor) { - super.changeCursor(cursor); - resetMappings(); - } - - /** - * Resets list-cursor mapping. - */ - public void reset() { - resetMappings(); - notifyDataSetChanged(); - } - - private void resetMappings() { - mListMapping.clear(); - mRemovedCursorPositions.clear(); - } - - @Override - public Object getItem(int position) { - return super.getItem(mListMapping.get(position, position)); - } - - @Override - public long getItemId(int position) { - return super.getItemId(mListMapping.get(position, position)); - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - return super.getDropDownView(mListMapping.get(position, position), convertView, parent); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - return super.getView(mListMapping.get(position, position), convertView, parent); - } - - /** - * On drop, this updates the mapping between Cursor positions - * and ListView positions. The Cursor is unchanged. Retrieve - * the current mapping with {@link getCursorPositions()}. - * - * @see DragSortListView.DropListener#drop(int, int) - */ - @Override - public void drop(int from, int to) { - if (from != to) { - int cursorFrom = mListMapping.get(from, from); - - if (from > to) { - for (int i = from; i > to; --i) { - mListMapping.put(i, mListMapping.get(i - 1, i - 1)); - } - } else { - for (int i = from; i < to; ++i) { - mListMapping.put(i, mListMapping.get(i + 1, i + 1)); - } - } - mListMapping.put(to, cursorFrom); - - cleanMapping(); - notifyDataSetChanged(); - } - } - - /** - * On remove, this updates the mapping between Cursor positions - * and ListView positions. The Cursor is unchanged. Retrieve - * the current mapping with {@link getCursorPositions()}. - * - * @see DragSortListView.RemoveListener#remove(int) - */ - @Override - public void remove(int which) { - int cursorPos = mListMapping.get(which, which); - if (!mRemovedCursorPositions.contains(cursorPos)) { - mRemovedCursorPositions.add(cursorPos); - } - - int newCount = getCount(); - for (int i = which; i < newCount; ++i) { - mListMapping.put(i, mListMapping.get(i + 1, i + 1)); - } - - mListMapping.delete(newCount); - - cleanMapping(); - notifyDataSetChanged(); - } - - /** - * Does nothing. Just completes DragSortListener interface. - */ - @Override - public void drag(int from, int to) { - // do nothing - } - - /** - * Remove unnecessary mappings from sparse array. - */ - private void cleanMapping() { - ArrayList toRemove = new ArrayList(); - - int size = mListMapping.size(); - for (int i = 0; i < size; ++i) { - if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) { - toRemove.add(mListMapping.keyAt(i)); - } - } - - size = toRemove.size(); - for (int i = 0; i < size; ++i) { - mListMapping.delete(toRemove.get(i)); - } - } - - @Override - public int getCount() { - return super.getCount() - mRemovedCursorPositions.size(); - } - - /** - * Get the Cursor position mapped to by the provided list position - * (given all previously handled drag-sort - * operations). - * - * @param position List position - * - * @return The mapped-to Cursor position - */ - public int getCursorPosition(int position) { - return mListMapping.get(position, position); - } - - /** - * Get the current order of Cursor positions presented by the - * list. - */ - public ArrayList getCursorPositions() { - ArrayList result = new ArrayList(); - - for (int i = 0; i < getCount(); ++i) { - result.add(mListMapping.get(i, i)); - } - - return result; - } - - /** - * Get the list position mapped to by the provided Cursor position. - * If the provided Cursor position has been removed by a drag-sort, - * this returns {@link #REMOVED}. - * - * @param cursorPosition A Cursor position - * @return The mapped-to list position or REMOVED - */ - public int getListPosition(int cursorPosition) { - if (mRemovedCursorPositions.contains(cursorPosition)) { - return REMOVED; - } - - int index = mListMapping.indexOfValue(cursorPosition); - if (index < 0) { - return cursorPosition; - } else { - return mListMapping.keyAt(index); - } - } - - -} diff --git a/core/library/src/main/java/com/mobeta/android/dslv/DragSortItemView.java b/core/library/src/main/java/com/mobeta/android/dslv/DragSortItemView.java deleted file mode 100644 index 1cd9b2a8..00000000 --- a/core/library/src/main/java/com/mobeta/android/dslv/DragSortItemView.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.mobeta.android.dslv; - -import android.content.Context; -import android.view.Gravity; -import android.view.View; -import android.view.View.MeasureSpec; -import android.view.ViewGroup; -import android.widget.AbsListView; -import timber.log.Timber; - -/** - * Lightweight ViewGroup that wraps list items obtained from user's - * ListAdapter. ItemView expects a single child that has a definite - * height (i.e. the child's layout height is not MATCH_PARENT). - * The width of - * ItemView will always match the width of its child (that is, - * the width MeasureSpec given to ItemView is passed directly - * to the child, and the ItemView measured width is set to the - * child's measured width). The height of ItemView can be anything; - * the - * - * - * The purpose of this class is to optimize slide - * shuffle animations. - */ -public class DragSortItemView extends ViewGroup { - - private int mGravity = Gravity.TOP; - - public DragSortItemView(Context context) { - super(context); - - // always init with standard ListView layout params - setLayoutParams(new AbsListView.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - - //setClipChildren(true); - } - - public void setGravity(int gravity) { - mGravity = gravity; - } - - public int getGravity() { - return mGravity; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - final View child = getChildAt(0); - - if (child == null) { - return; - } - - if (mGravity == Gravity.TOP) { - child.layout(0, 0, getMeasuredWidth(), child.getMeasuredHeight()); - } else { - child.layout(0, getMeasuredHeight() - child.getMeasuredHeight(), getMeasuredWidth(), getMeasuredHeight()); - } - } - - /** - * - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - - int height = MeasureSpec.getSize(heightMeasureSpec); - int width = MeasureSpec.getSize(widthMeasureSpec); - - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - - final View child = getChildAt(0); - if (child == null) { - setMeasuredDimension(0, width); - return; - } - - if (child.isLayoutRequested()) { - // Always let child be as tall as it wants. - measureChild(child, widthMeasureSpec, - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - } - - if (heightMode == MeasureSpec.UNSPECIFIED) { - ViewGroup.LayoutParams lp = getLayoutParams(); - - if (lp.height > 0) { - height = lp.height; - } else { - height = child.getMeasuredHeight(); - } - } - - setMeasuredDimension(width, height); - } - -} diff --git a/core/library/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java b/core/library/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java deleted file mode 100644 index e4cdeccd..00000000 --- a/core/library/src/main/java/com/mobeta/android/dslv/DragSortItemViewCheckable.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.mobeta.android.dslv; - -import android.content.Context; -import android.view.Gravity; -import android.view.View; -import android.view.View.MeasureSpec; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.Checkable; -import timber.log.Timber; - -/** - * Lightweight ViewGroup that wraps list items obtained from user's - * ListAdapter. ItemView expects a single child that has a definite - * height (i.e. the child's layout height is not MATCH_PARENT). - * The width of - * ItemView will always match the width of its child (that is, - * the width MeasureSpec given to ItemView is passed directly - * to the child, and the ItemView measured width is set to the - * child's measured width). The height of ItemView can be anything; - * the - * - * - * The purpose of this class is to optimize slide - * shuffle animations. - */ -public class DragSortItemViewCheckable extends DragSortItemView implements Checkable { - - public DragSortItemViewCheckable(Context context) { - super(context); - } - - @Override - public boolean isChecked() { - View child = getChildAt(0); - if (child instanceof Checkable) - return ((Checkable) child).isChecked(); - else - return false; - } - - @Override - public void setChecked(boolean checked) { - View child = getChildAt(0); - if (child instanceof Checkable) - ((Checkable) child).setChecked(checked); - } - - @Override - public void toggle() { - View child = getChildAt(0); - if (child instanceof Checkable) - ((Checkable) child).toggle(); - } -} diff --git a/core/library/src/main/java/com/mobeta/android/dslv/DragSortListView.java b/core/library/src/main/java/com/mobeta/android/dslv/DragSortListView.java deleted file mode 100644 index 01d9074e..00000000 --- a/core/library/src/main/java/com/mobeta/android/dslv/DragSortListView.java +++ /dev/null @@ -1,3042 +0,0 @@ -/* - * DragSortListView. - * - * A subclass of the Android ListView component that enables drag - * and drop re-ordering of list items. - * - * Copyright 2012 Carl Bauer - * - * 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 com.mobeta.android.dslv; - -import android.content.Context; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.drawable.Drawable; -import android.os.Environment; -import android.os.SystemClock; -import android.util.AttributeSet; -import timber.log.Timber; -import android.util.SparseBooleanArray; -import android.util.SparseIntArray; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.BaseAdapter; -import android.widget.Checkable; -import android.widget.ListAdapter; -import android.widget.ListView; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; - -/** - * ListView subclass that mediates drag and drop resorting of items. - * - * - * @author heycosmo - * - */ -public class DragSortListView extends ListView { - - - /** - * The View that floats above the ListView and represents - * the dragged item. - */ - private View mFloatView; - - /** - * The float View location. First based on touch location - * and given deltaX and deltaY. Then restricted by callback - * to FloatViewManager.onDragFloatView(). Finally restricted - * by bounds of DSLV. - */ - private Point mFloatLoc = new Point(); - - private Point mTouchLoc = new Point(); - - /** - * The middle (in the y-direction) of the floating View. - */ - private int mFloatViewMid; - - /** - * Flag to make sure float View isn't measured twice - */ - private boolean mFloatViewOnMeasured = false; - - /** - * Watch the Adapter for data changes. Cancel a drag if - * coincident with a change. - */ - private DataSetObserver mObserver; - - /** - * Transparency for the floating View (XML attribute). - */ - private float mFloatAlpha = 1.0f; - private float mCurrFloatAlpha = 1.0f; - - /** - * While drag-sorting, the current position of the floating - * View. If dropped, the dragged item will land in this position. - */ - private int mFloatPos; - - /** - * The first expanded ListView position that helps represent - * the drop slot tracking the floating View. - */ - private int mFirstExpPos; - - /** - * The second expanded ListView position that helps represent - * the drop slot tracking the floating View. This can equal - * mFirstExpPos if there is no slide shuffle occurring; otherwise - * it is equal to mFirstExpPos + 1. - */ - private int mSecondExpPos; - - /** - * Flag set if slide shuffling is enabled. - */ - private boolean mAnimate = false; - - /** - * The user dragged from this position. - */ - private int mSrcPos; - - /** - * Offset (in x) within the dragged item at which the user - * picked it up (or first touched down with the digitalis). - */ - private int mDragDeltaX; - - /** - * Offset (in y) within the dragged item at which the user - * picked it up (or first touched down with the digitalis). - */ - private int mDragDeltaY; - - - /** - * The difference (in x) between screen coordinates and coordinates - * in this view. - */ - private int mOffsetX; - - /** - * The difference (in y) between screen coordinates and coordinates - * in this view. - */ - private int mOffsetY; - - /** - * A listener that receives callbacks whenever the floating View - * hovers over a new position. - */ - private DragListener mDragListener; - - /** - * A listener that receives a callback when the floating View - * is dropped. - */ - private DropListener mDropListener; - - /** - * A listener that receives a callback when the floating View - * (or more precisely the originally dragged item) is removed - * by one of the provided gestures. - */ - private RemoveListener mRemoveListener; - - /** - * Enable/Disable item dragging - * - * @attr name dslv:drag_enabled - */ - private boolean mDragEnabled = true; - - /** - * Drag state enum. - */ - private final static int IDLE = 0; - private final static int REMOVING = 1; - private final static int DROPPING = 2; - private final static int STOPPED = 3; - private final static int DRAGGING = 4; - - private int mDragState = IDLE; - - /** - * Height in pixels to which the originally dragged item - * is collapsed during a drag-sort. Currently, this value - * must be greater than zero. - */ - private int mItemHeightCollapsed = 1; - - /** - * Height of the floating View. Stored for the purpose of - * providing the tracking drop slot. - */ - private int mFloatViewHeight; - - /** - * Convenience member. See above. - */ - private int mFloatViewHeightHalf; - - /** - * Save the given width spec for use in measuring children - */ - private int mWidthMeasureSpec = 0; - - /** - * Sample Views ultimately used for calculating the height - * of ListView items that are off-screen. - */ - private View[] mSampleViewTypes = new View[1]; - - /** - * Drag-scroll encapsulator! - */ - private DragScroller mDragScroller; - - /** - * Determines the start of the upward drag-scroll region - * at the top of the ListView. Specified by a fraction - * of the ListView height, thus screen resolution agnostic. - */ - private float mDragUpScrollStartFrac = 1.0f / 3.0f; - - /** - * Determines the start of the downward drag-scroll region - * at the bottom of the ListView. Specified by a fraction - * of the ListView height, thus screen resolution agnostic. - */ - private float mDragDownScrollStartFrac = 1.0f / 3.0f; - - /** - * The following are calculated from the above fracs. - */ - private int mUpScrollStartY; - private int mDownScrollStartY; - private float mDownScrollStartYF; - private float mUpScrollStartYF; - - /** - * Calculated from above above and current ListView height. - */ - private float mDragUpScrollHeight; - - /** - * Calculated from above above and current ListView height. - */ - private float mDragDownScrollHeight; - - /** - * Maximum drag-scroll speed in pixels per ms. Only used with - * default linear drag-scroll profile. - */ - private float mMaxScrollSpeed = 0.5f; - - /** - * Defines the scroll speed during a drag-scroll. User can - * provide their own; this default is a simple linear profile - * where scroll speed increases linearly as the floating View - * nears the top/bottom of the ListView. - */ - private DragScrollProfile mScrollProfile = new DragScrollProfile() { - @Override - public float getSpeed(float w, long t) { - return mMaxScrollSpeed * w; - } - }; - - /** - * Current touch x. - */ - private int mX; - - /** - * Current touch y. - */ - private int mY; - - /** - * Last touch x. - */ - private int mLastX; - - /** - * Last touch y. - */ - private int mLastY; - - /** - * The touch y-coord at which drag started - */ - private int mDragStartY; - - /** - * Drag flag bit. Floating View can move in the positive - * x direction. - */ - public final static int DRAG_POS_X = 0x1; - - /** - * Drag flag bit. Floating View can move in the negative - * x direction. - */ - public final static int DRAG_NEG_X = 0x2; - - /** - * Drag flag bit. Floating View can move in the positive - * y direction. This is subtle. What this actually means is - * that, if enabled, the floating View can be dragged below its starting - * position. Remove in favor of upper-bounding item position? - */ - public final static int DRAG_POS_Y = 0x4; - - /** - * Drag flag bit. Floating View can move in the negative - * y direction. This is subtle. What this actually means is - * that the floating View can be dragged above its starting - * position. Remove in favor of lower-bounding item position? - */ - public final static int DRAG_NEG_Y = 0x8; - - /** - * Flags that determine limits on the motion of the - * floating View. See flags above. - */ - private int mDragFlags = 0; - - /** - * Last call to an on*TouchEvent was a call to - * onInterceptTouchEvent. - */ - private boolean mLastCallWasIntercept = false; - - /** - * A touch event is in progress. - */ - private boolean mInTouchEvent = false; - - /** - * Let the user customize the floating View. - */ - private FloatViewManager mFloatViewManager = null; - - /** - * Given to ListView to cancel its action when a drag-sort - * begins. - */ - private MotionEvent mCancelEvent; - - /** - * Enum telling where to cancel the ListView action when a - * drag-sort begins - */ - private static final int NO_CANCEL = 0; - private static final int ON_TOUCH_EVENT = 1; - private static final int ON_INTERCEPT_TOUCH_EVENT = 2; - - /** - * Where to cancel the ListView action when a - * drag-sort begins - */ - private int mCancelMethod = NO_CANCEL; - - /** - * Determines when a slide shuffle animation starts. That is, - * defines how close to the edge of the drop slot the floating - * View must be to initiate the slide. - */ - private float mSlideRegionFrac = 0.25f; - - /** - * Number between 0 and 1 indicating the relative location of - * a sliding item (only used if drag-sort animations - * are turned on). Nearly 1 means the item is - * at the top of the slide region (nearly full blank item - * is directly below). - */ - private float mSlideFrac = 0.0f; - - /** - * Wraps the user-provided ListAdapter. This is used to wrap each - * item View given by the user inside another View (currenly - * a RelativeLayout) which - * expands and collapses to simulate the item shuffling. - */ - private AdapterWrapper mAdapterWrapper; - - /** - * Turn on custom debugger. - */ - private boolean mTrackDragSort = false; - - /** - * Debugging class. - */ - private DragSortTracker mDragSortTracker; - - /** - * Needed for adjusting item heights from within layoutChildren - */ - private boolean mBlockLayoutRequests = false; - - /** - * Set to true when a down event happens during drag sort; - * for example, when drag finish animations are - * playing. - */ - private boolean mIgnoreTouchEvent = false; - - /** - * Caches DragSortItemView child heights. Sometimes DSLV has to - * know the height of an offscreen item. Since ListView virtualizes - * these, DSLV must get the item from the ListAdapter to obtain - * its height. That process can be expensive, but often the same - * offscreen item will be requested many times in a row. Once an - * offscreen item height is calculated, we cache it in this guy. - * Actually, we cache the height of the child of the - * DragSortItemView since the item height changes often during a - * drag-sort. - */ - private static final int sCacheSize = 3; - private HeightCache mChildHeightCache = new HeightCache(sCacheSize); - - private RemoveAnimator mRemoveAnimator; - - private LiftAnimator mLiftAnimator; - - private DropAnimator mDropAnimator; - - private boolean mUseRemoveVelocity; - private float mRemoveVelocityX = 0; - - public DragSortListView(Context context, AttributeSet attrs) { - super(context, attrs); - - int defaultDuration = 150; - int removeAnimDuration = defaultDuration; // ms - int dropAnimDuration = defaultDuration; // ms - - if (attrs != null) { - TypedArray a = getContext().obtainStyledAttributes(attrs, - R.styleable.DragSortListView, 0, 0); - - mItemHeightCollapsed = Math.max(1, a.getDimensionPixelSize( - R.styleable.DragSortListView_collapsed_height, 1)); - - mTrackDragSort = a.getBoolean( - R.styleable.DragSortListView_track_drag_sort, false); - - if (mTrackDragSort) { - mDragSortTracker = new DragSortTracker(); - } - - // alpha between 0 and 255, 0=transparent, 255=opaque - mFloatAlpha = a.getFloat(R.styleable.DragSortListView_float_alpha, mFloatAlpha); - mCurrFloatAlpha = mFloatAlpha; - - mDragEnabled = a.getBoolean(R.styleable.DragSortListView_drag_enabled, mDragEnabled); - - mSlideRegionFrac = Math.max(0.0f, - Math.min(1.0f, 1.0f - a.getFloat( - R.styleable.DragSortListView_slide_shuffle_speed, - 0.75f))); - - mAnimate = mSlideRegionFrac > 0.0f; - - float frac = a.getFloat( - R.styleable.DragSortListView_drag_scroll_start, - mDragUpScrollStartFrac); - - setDragScrollStart(frac); - - mMaxScrollSpeed = a.getFloat( - R.styleable.DragSortListView_max_drag_scroll_speed, - mMaxScrollSpeed); - - removeAnimDuration = a.getInt( - R.styleable.DragSortListView_remove_animation_duration, - removeAnimDuration); - - dropAnimDuration = a.getInt( - R.styleable.DragSortListView_drop_animation_duration, - dropAnimDuration); - - boolean useDefault = a.getBoolean( - R.styleable.DragSortListView_use_default_controller, - true); - - if (useDefault) { - boolean removeEnabled = a.getBoolean( - R.styleable.DragSortListView_remove_enabled, - false); - int removeMode = a.getInt( - R.styleable.DragSortListView_remove_mode, - DragSortController.FLING_REMOVE); - boolean sortEnabled = a.getBoolean( - R.styleable.DragSortListView_sort_enabled, - true); - int dragInitMode = a.getInt( - R.styleable.DragSortListView_drag_start_mode, - DragSortController.ON_DOWN); - int dragHandleId = a.getResourceId( - R.styleable.DragSortListView_drag_handle_id, - 0); - int flingHandleId = a.getResourceId( - R.styleable.DragSortListView_fling_handle_id, - 0); - int clickRemoveId = a.getResourceId( - R.styleable.DragSortListView_click_remove_id, - 0); - int bgColor = a.getColor( - R.styleable.DragSortListView_float_background_color, - Color.BLACK); - - DragSortController controller = new DragSortController( - this, dragHandleId, dragInitMode, removeMode, - clickRemoveId, flingHandleId); - controller.setRemoveEnabled(removeEnabled); - controller.setSortEnabled(sortEnabled); - controller.setBackgroundColor(bgColor); - - mFloatViewManager = controller; - setOnTouchListener(controller); - } - - a.recycle(); - } - - mDragScroller = new DragScroller(); - - float smoothness = 0.5f; - if (removeAnimDuration > 0) { - mRemoveAnimator = new RemoveAnimator(smoothness, removeAnimDuration); - } - // mLiftAnimator = new LiftAnimator(smoothness, 100); - if (dropAnimDuration > 0) { - mDropAnimator = new DropAnimator(smoothness, dropAnimDuration); - } - - mCancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0f, 0f, 0, 0f, - 0f, 0, 0); - - // construct the dataset observer - mObserver = new DataSetObserver() { - private void cancel() { - if (mDragState == DRAGGING) { - cancelDrag(); - } - } - - @Override - public void onChanged() { - cancel(); - } - - @Override - public void onInvalidated() { - cancel(); - } - }; - } - - /** - * Usually called from a FloatViewManager. The float alpha - * will be reset to the xml-defined value every time a drag - * is stopped. - */ - public void setFloatAlpha(float alpha) { - mCurrFloatAlpha = alpha; - } - - public float getFloatAlpha() { - return mCurrFloatAlpha; - } - - /** - * Set maximum drag scroll speed in positions/second. Only applies - * if using default ScrollSpeedProfile. - * - * @param max Maximum scroll speed. - */ - public void setMaxScrollSpeed(float max) { - mMaxScrollSpeed = max; - } - - /** - * For each DragSortListView Listener interface implemented by - * adapter, this method calls the appropriate - * set*Listener method with adapter as the argument. - * - * @param adapter The ListAdapter providing data to back - * DragSortListView. - * - * @see android.widget.ListView#setAdapter(android.widget.ListAdapter) - */ - @Override - public void setAdapter(ListAdapter adapter) { - if (adapter != null) { - mAdapterWrapper = new AdapterWrapper(adapter); - adapter.registerDataSetObserver(mObserver); - - if (adapter instanceof DropListener) { - setDropListener((DropListener) adapter); - } - if (adapter instanceof DragListener) { - setDragListener((DragListener) adapter); - } - if (adapter instanceof RemoveListener) { - setRemoveListener((RemoveListener) adapter); - } - } else { - mAdapterWrapper = null; - } - - super.setAdapter(mAdapterWrapper); - } - - /** - * As opposed to {@link ListView#getAdapter()}, which returns - * a heavily wrapped ListAdapter (DragSortListView wraps the - * input ListAdapter {\emph and} ListView wraps the wrapped one). - * - * @return The ListAdapter set as the argument of {@link setAdapter()} - */ - public ListAdapter getInputAdapter() { - if (mAdapterWrapper == null) { - return null; - } else { - return mAdapterWrapper.getAdapter(); - } - } - - private class AdapterWrapper extends BaseAdapter { - private ListAdapter mAdapter; - - public AdapterWrapper(ListAdapter adapter) { - super(); - mAdapter = adapter; - - mAdapter.registerDataSetObserver(new DataSetObserver() { - public void onChanged() { - notifyDataSetChanged(); - } - - public void onInvalidated() { - notifyDataSetInvalidated(); - } - }); - } - - public ListAdapter getAdapter() { - return mAdapter; - } - - @Override - public long getItemId(int position) { - return mAdapter.getItemId(position); - } - - @Override - public Object getItem(int position) { - return mAdapter.getItem(position); - } - - @Override - public int getCount() { - return mAdapter.getCount(); - } - - @Override - public boolean areAllItemsEnabled() { - return mAdapter.areAllItemsEnabled(); - } - - @Override - public boolean isEnabled(int position) { - return mAdapter.isEnabled(position); - } - - @Override - public int getItemViewType(int position) { - return mAdapter.getItemViewType(position); - } - - @Override - public int getViewTypeCount() { - return mAdapter.getViewTypeCount(); - } - - @Override - public boolean hasStableIds() { - return mAdapter.hasStableIds(); - } - - @Override - public boolean isEmpty() { - return mAdapter.isEmpty(); - } - - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - DragSortItemView v; - View child; - if (convertView != null) { - v = (DragSortItemView) convertView; - View oldChild = v.getChildAt(0); - - child = mAdapter.getView(position, oldChild, DragSortListView.this); - if (child != oldChild) { - // shouldn't get here if user is reusing convertViews - // properly - if (oldChild != null) { - v.removeViewAt(0); - } - v.addView(child); - } - } else { - child = mAdapter.getView(position, null, DragSortListView.this); - if (child instanceof Checkable) { - v = new DragSortItemViewCheckable(getContext()); - } else { - v = new DragSortItemView(getContext()); - } - v.setLayoutParams(new AbsListView.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - v.addView(child); - } - - // Set the correct item height given drag state; passed - // View needs to be measured if measurement is required. - adjustItem(position + getHeaderViewsCount(), v, true); - - return v; - } - } - - private void drawDivider(int expPosition, Canvas canvas) { - - final Drawable divider = getDivider(); - final int dividerHeight = getDividerHeight(); - - if (divider != null && dividerHeight != 0) { - final ViewGroup expItem = (ViewGroup) getChildAt(expPosition - - getFirstVisiblePosition()); - if (expItem != null) { - final int l = getPaddingLeft(); - final int r = getWidth() - getPaddingRight(); - final int t; - final int b; - - final int childHeight = expItem.getChildAt(0).getHeight(); - - if (expPosition > mSrcPos) { - t = expItem.getTop() + childHeight; - b = t + dividerHeight; - } else { - b = expItem.getBottom() - childHeight; - t = b - dividerHeight; - } - // Have to clip to support ColorDrawable on <= Gingerbread - canvas.save(); - canvas.clipRect(l, t, r, b); - divider.setBounds(l, t, r, b); - divider.draw(canvas); - canvas.restore(); - } - } - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - - if (mDragState != IDLE) { - // draw the divider over the expanded item - if (mFirstExpPos != mSrcPos) { - drawDivider(mFirstExpPos, canvas); - } - if (mSecondExpPos != mFirstExpPos && mSecondExpPos != mSrcPos) { - drawDivider(mSecondExpPos, canvas); - } - } - - if (mFloatView != null) { - // draw the float view over everything - final int w = mFloatView.getWidth(); - final int h = mFloatView.getHeight(); - - int x = mFloatLoc.x; - - int width = getWidth(); - if (x < 0) - x = -x; - float alphaMod; - if (x < width) { - alphaMod = ((float) (width - x)) / ((float) width); - alphaMod *= alphaMod; - } else { - alphaMod = 0; - } - - final int alpha = (int) (255f * mCurrFloatAlpha * alphaMod); - - canvas.save(); - canvas.translate(mFloatLoc.x, mFloatLoc.y); - canvas.clipRect(0, 0, w, h); - - canvas.saveLayerAlpha(0, 0, w, h, alpha, Canvas.ALL_SAVE_FLAG); - mFloatView.draw(canvas); - canvas.restore(); - canvas.restore(); - } - } - - private int getItemHeight(int position) { - View v = getChildAt(position - getFirstVisiblePosition()); - - if (v != null) { - // item is onscreen, just get the height of the View - return v.getHeight(); - } else { - // item is offscreen. get child height and calculate - // item height based on current shuffle state - return calcItemHeight(position, getChildHeight(position)); - } - } - - private void printPosData() { - Timber.d("mSrcPos=%i mFirstExpPos=%i mSecondExpPos=%i", mSrcPos, mFirstExpPos, mSecondExpPos); - } - - private class HeightCache { - - private SparseIntArray mMap; - private ArrayList mOrder; - private int mMaxSize; - - public HeightCache(int size) { - mMap = new SparseIntArray(size); - mOrder = new ArrayList(size); - mMaxSize = size; - } - - /** - * Add item height at position if doesn't already exist. - */ - public void add(int position, int height) { - int currHeight = mMap.get(position, -1); - if (currHeight != height) { - if (currHeight == -1) { - if (mMap.size() == mMaxSize) { - // remove oldest entry - mMap.delete(mOrder.remove(0)); - } - } else { - // move position to newest slot - mOrder.remove((Integer) position); - } - mMap.put(position, height); - mOrder.add(position); - } - } - - public int get(int position) { - return mMap.get(position, -1); - } - - public void clear() { - mMap.clear(); - mOrder.clear(); - } - - } - - /** - * Get the shuffle edge for item at position when top of - * item is at y-coord top. Assumes that current item heights - * are consistent with current float view location and - * thus expanded positions and slide fraction. i.e. Should not be - * called between update of expanded positions/slide fraction - * and layoutChildren. - * - * @param position - * @param top - * @param height Height of item at position. If -1, this function - * calculates this height. - * - * @return Shuffle line between position-1 and position (for - * the given view of the list; that is, for when top of item at - * position has y-coord of given `top`). If - * floating View (treated as horizontal line) is dropped - * immediately above this line, it lands in position-1. If - * dropped immediately below this line, it lands in position. - */ - private int getShuffleEdge(int position, int top) { - - final int numHeaders = getHeaderViewsCount(); - final int numFooters = getFooterViewsCount(); - - // shuffle edges are defined between items that can be - // dragged; there are N-1 of them if there are N draggable - // items. - - if (position <= numHeaders || (position >= getCount() - numFooters)) { - return top; - } - - int divHeight = getDividerHeight(); - - int edge; - - int maxBlankHeight = mFloatViewHeight - mItemHeightCollapsed; - int childHeight = getChildHeight(position); - int itemHeight = getItemHeight(position); - - // first calculate top of item given that floating View is - // centered over src position - int otop = top; - if (mSecondExpPos <= mSrcPos) { - // items are expanded on and/or above the source position - - if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) { - if (position == mSrcPos) { - otop = top + itemHeight - mFloatViewHeight; - } else { - int blankHeight = itemHeight - childHeight; - otop = top + blankHeight - maxBlankHeight; - } - } else if (position > mSecondExpPos && position <= mSrcPos) { - otop = top - maxBlankHeight; - } - - } else { - // items are expanded on and/or below the source position - - if (position > mSrcPos && position <= mFirstExpPos) { - otop = top + maxBlankHeight; - } else if (position == mSecondExpPos && mFirstExpPos != mSecondExpPos) { - int blankHeight = itemHeight - childHeight; - otop = top + blankHeight; - } - } - - // otop is set - if (position <= mSrcPos) { - edge = otop + (mFloatViewHeight - divHeight - getChildHeight(position - 1)) / 2; - } else { - edge = otop + (childHeight - divHeight - mFloatViewHeight) / 2; - } - - return edge; - } - - private boolean updatePositions() { - - final int first = getFirstVisiblePosition(); - int startPos = mFirstExpPos; - View startView = getChildAt(startPos - first); - - if (startView == null) { - startPos = first + getChildCount() / 2; - startView = getChildAt(startPos - first); - } - int startTop = startView.getTop(); - - int itemHeight = startView.getHeight(); - - int edge = getShuffleEdge(startPos, startTop); - int lastEdge = edge; - - int divHeight = getDividerHeight(); - - - int itemPos = startPos; - int itemTop = startTop; - if (mFloatViewMid < edge) { - // scanning up for float position - while (itemPos >= 0) { - itemPos--; - itemHeight = getItemHeight(itemPos); - - if (itemPos == 0) { - edge = itemTop - divHeight - itemHeight; - break; - } - - itemTop -= itemHeight + divHeight; - edge = getShuffleEdge(itemPos, itemTop); - - if (mFloatViewMid >= edge) { - break; - } - - lastEdge = edge; - } - } else { - // scanning down for float position - final int count = getCount(); - while (itemPos < count) { - if (itemPos == count - 1) { - edge = itemTop + divHeight + itemHeight; - break; - } - - itemTop += divHeight + itemHeight; - itemHeight = getItemHeight(itemPos + 1); - edge = getShuffleEdge(itemPos + 1, itemTop); - - // test for hit - if (mFloatViewMid < edge) { - break; - } - - lastEdge = edge; - itemPos++; - } - } - - final int numHeaders = getHeaderViewsCount(); - final int numFooters = getFooterViewsCount(); - - boolean updated = false; - - int oldFirstExpPos = mFirstExpPos; - int oldSecondExpPos = mSecondExpPos; - float oldSlideFrac = mSlideFrac; - - if (mAnimate) { - int edgeToEdge = Math.abs(edge - lastEdge); - - int edgeTop, edgeBottom; - if (mFloatViewMid < edge) { - edgeBottom = edge; - edgeTop = lastEdge; - } else { - edgeTop = edge; - edgeBottom = lastEdge; - } - - int slideRgnHeight = (int) (0.5f * mSlideRegionFrac * edgeToEdge); - float slideRgnHeightF = (float) slideRgnHeight; - int slideEdgeTop = edgeTop + slideRgnHeight; - int slideEdgeBottom = edgeBottom - slideRgnHeight; - - // Three regions - if (mFloatViewMid < slideEdgeTop) { - mFirstExpPos = itemPos - 1; - mSecondExpPos = itemPos; - mSlideFrac = 0.5f * ((float) (slideEdgeTop - mFloatViewMid)) / slideRgnHeightF; - } else if (mFloatViewMid < slideEdgeBottom) { - mFirstExpPos = itemPos; - mSecondExpPos = itemPos; - } else { - mFirstExpPos = itemPos; - mSecondExpPos = itemPos + 1; - mSlideFrac = 0.5f * (1.0f + ((float) (edgeBottom - mFloatViewMid)) - / slideRgnHeightF); - } - - } else { - mFirstExpPos = itemPos; - mSecondExpPos = itemPos; - } - - // correct for headers and footers - if (mFirstExpPos < numHeaders) { - itemPos = numHeaders; - mFirstExpPos = itemPos; - mSecondExpPos = itemPos; - } else if (mSecondExpPos >= getCount() - numFooters) { - itemPos = getCount() - numFooters - 1; - mFirstExpPos = itemPos; - mSecondExpPos = itemPos; - } - - if (mFirstExpPos != oldFirstExpPos || mSecondExpPos != oldSecondExpPos - || mSlideFrac != oldSlideFrac) { - updated = true; - } - - if (itemPos != mFloatPos) { - if (mDragListener != null) { - mDragListener.drag(mFloatPos - numHeaders, itemPos - numHeaders); - } - - mFloatPos = itemPos; - updated = true; - } - - return updated; - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (mTrackDragSort) { - mDragSortTracker.appendState(); - } - } - - private class SmoothAnimator implements Runnable { - protected long mStartTime; - - private float mDurationF; - - private float mAlpha; - private float mA, mB, mC, mD; - - private boolean mCanceled; - - public SmoothAnimator(float smoothness, int duration) { - mAlpha = smoothness; - mDurationF = (float) duration; - mA = mD = 1f / (2f * mAlpha * (1f - mAlpha)); - mB = mAlpha / (2f * (mAlpha - 1f)); - mC = 1f / (1f - mAlpha); - } - - public float transform(float frac) { - if (frac < mAlpha) { - return mA * frac * frac; - } else if (frac < 1f - mAlpha) { - return mB + mC * frac; - } else { - return 1f - mD * (frac - 1f) * (frac - 1f); - } - } - - public void start() { - mStartTime = SystemClock.uptimeMillis(); - mCanceled = false; - onStart(); - post(this); - } - - public void cancel() { - mCanceled = true; - } - - public void onStart() { - // stub - } - - public void onUpdate(float frac, float smoothFrac) { - // stub - } - - public void onStop() { - // stub - } - - @Override - public void run() { - if (mCanceled) { - return; - } - - float fraction = ((float) (SystemClock.uptimeMillis() - mStartTime)) / mDurationF; - - if (fraction >= 1f) { - onUpdate(1f, 1f); - onStop(); - } else { - onUpdate(fraction, transform(fraction)); - post(this); - } - } - } - - /** - * Centers floating View under touch point. - */ - private class LiftAnimator extends SmoothAnimator { - - private float mInitDragDeltaY; - private float mFinalDragDeltaY; - - public LiftAnimator(float smoothness, int duration) { - super(smoothness, duration); - } - - @Override - public void onStart() { - mInitDragDeltaY = mDragDeltaY; - mFinalDragDeltaY = mFloatViewHeightHalf; - } - - @Override - public void onUpdate(float frac, float smoothFrac) { - if (mDragState != DRAGGING) { - cancel(); - } else { - mDragDeltaY = (int) (smoothFrac * mFinalDragDeltaY + (1f - smoothFrac) - * mInitDragDeltaY); - mFloatLoc.y = mY - mDragDeltaY; - doDragFloatView(true); - } - } - } - - /** - * Centers floating View over drop slot before destroying. - */ - private class DropAnimator extends SmoothAnimator { - - private int mDropPos; - private int srcPos; - private float mInitDeltaY; - private float mInitDeltaX; - - public DropAnimator(float smoothness, int duration) { - super(smoothness, duration); - } - - @Override - public void onStart() { - mDropPos = mFloatPos; - srcPos = mSrcPos; - mDragState = DROPPING; - mInitDeltaY = mFloatLoc.y - getTargetY(); - mInitDeltaX = mFloatLoc.x - getPaddingLeft(); - } - - private int getTargetY() { - final int first = getFirstVisiblePosition(); - final int otherAdjust = (mItemHeightCollapsed + getDividerHeight()) / 2; - View v = getChildAt(mDropPos - first); - int targetY = -1; - if (v != null) { - if (mDropPos == srcPos) { - targetY = v.getTop(); - } else if (mDropPos < srcPos) { - // expanded down - targetY = v.getTop() - otherAdjust; - } else { - // expanded up - targetY = v.getBottom() + otherAdjust - mFloatViewHeight; - } - } else { - // drop position is not on screen?? no animation - cancel(); - } - - return targetY; - } - - @Override - public void onUpdate(float frac, float smoothFrac) { - final int targetY = getTargetY(); - final int targetX = getPaddingLeft(); - final float deltaY = mFloatLoc.y - targetY; - final float deltaX = mFloatLoc.x - targetX; - final float f = 1f - smoothFrac; - if (f < Math.abs(deltaY / mInitDeltaY) || f < Math.abs(deltaX / mInitDeltaX)) { - mFloatLoc.y = targetY + (int) (mInitDeltaY * f); - mFloatLoc.x = getPaddingLeft() + (int) (mInitDeltaX * f); - doDragFloatView(true); - } - } - - @Override - public void onStop() { - dropFloatView(); - } - - } - - /** - * Collapses expanded items. - */ - private class RemoveAnimator extends SmoothAnimator { - - private float mFloatLocX; - private float mFirstStartBlank; - private float mSecondStartBlank; - - private int mFirstChildHeight = -1; - private int mSecondChildHeight = -1; - - private int mFirstPos; - private int mSecondPos; - private int srcPos; - - public RemoveAnimator(float smoothness, int duration) { - super(smoothness, duration); - } - - @Override - public void onStart() { - mFirstChildHeight = -1; - mSecondChildHeight = -1; - mFirstPos = mFirstExpPos; - mSecondPos = mSecondExpPos; - srcPos = mSrcPos; - mDragState = REMOVING; - - mFloatLocX = mFloatLoc.x; - if (mUseRemoveVelocity) { - float minVelocity = 2f * getWidth(); - if (mRemoveVelocityX == 0) { - mRemoveVelocityX = (mFloatLocX < 0 ? -1 : 1) * minVelocity; - } else { - minVelocity *= 2; - if (mRemoveVelocityX < 0 && mRemoveVelocityX > -minVelocity) - mRemoveVelocityX = -minVelocity; - else if (mRemoveVelocityX > 0 && mRemoveVelocityX < minVelocity) - mRemoveVelocityX = minVelocity; - } - } else { - destroyFloatView(); - } - } - - @Override - public void onUpdate(float frac, float smoothFrac) { - float f = 1f - smoothFrac; - - final int firstVis = getFirstVisiblePosition(); - View item = getChildAt(mFirstPos - firstVis); - ViewGroup.LayoutParams lp; - int blank; - - if (mUseRemoveVelocity) { - float dt = (float) (SystemClock.uptimeMillis() - mStartTime) / 1000; - if (dt == 0) - return; - float dx = mRemoveVelocityX * dt; - int w = getWidth(); - mRemoveVelocityX += (mRemoveVelocityX > 0 ? 1 : -1) * dt * w; - mFloatLocX += dx; - mFloatLoc.x = (int) mFloatLocX; - if (mFloatLocX < w && mFloatLocX > -w) { - mStartTime = SystemClock.uptimeMillis(); - doDragFloatView(true); - return; - } - } - - if (item != null) { - if (mFirstChildHeight == -1) { - mFirstChildHeight = getChildHeight(mFirstPos, item, false); - mFirstStartBlank = (float) (item.getHeight() - mFirstChildHeight); - } - blank = Math.max((int) (f * mFirstStartBlank), 1); - lp = item.getLayoutParams(); - lp.height = mFirstChildHeight + blank; - item.setLayoutParams(lp); - } - if (mSecondPos != mFirstPos) { - item = getChildAt(mSecondPos - firstVis); - if (item != null) { - if (mSecondChildHeight == -1) { - mSecondChildHeight = getChildHeight(mSecondPos, item, false); - mSecondStartBlank = (float) (item.getHeight() - mSecondChildHeight); - } - blank = Math.max((int) (f * mSecondStartBlank), 1); - lp = item.getLayoutParams(); - lp.height = mSecondChildHeight + blank; - item.setLayoutParams(lp); - } - } - } - - @Override - public void onStop() { - doRemoveItem(); - } - } - - public void removeItem(int which) { - - mUseRemoveVelocity = false; - removeItem(which, 0); - } - - /** - * Removes an item from the list and animates the removal. - * - * @param which Position to remove (NOTE: headers/footers ignored! - * this is a position in your input ListAdapter). - * @param velocityX - */ - public void removeItem(int which, float velocityX) { - if (mDragState == IDLE || mDragState == DRAGGING) { - - if (mDragState == IDLE) { - // called from outside drag-sort - mSrcPos = getHeaderViewsCount() + which; - mFirstExpPos = mSrcPos; - mSecondExpPos = mSrcPos; - mFloatPos = mSrcPos; - View v = getChildAt(mSrcPos - getFirstVisiblePosition()); - if (v != null) { - v.setVisibility(View.INVISIBLE); - } - } - - mDragState = REMOVING; - mRemoveVelocityX = velocityX; - - if (mInTouchEvent) { - switch (mCancelMethod) { - case ON_TOUCH_EVENT: - super.onTouchEvent(mCancelEvent); - break; - case ON_INTERCEPT_TOUCH_EVENT: - super.onInterceptTouchEvent(mCancelEvent); - break; - } - } - - if (mRemoveAnimator != null) { - mRemoveAnimator.start(); - } else { - doRemoveItem(which); - } - } - } - - /** - * Move an item, bypassing the drag-sort process. Simply calls - * through to {@link DropListener#drop(int, int)}. - * - * @param from Position to move (NOTE: headers/footers ignored! - * this is a position in your input ListAdapter). - * @param to Target position (NOTE: headers/footers ignored! - * this is a position in your input ListAdapter). - */ - public void moveItem(int from, int to) { - if (mDropListener != null) { - final int count = getInputAdapter().getCount(); - if (from >= 0 && from < count && to >= 0 && to < count) { - mDropListener.drop(from, to); - } - } - } - - /** - * Cancel a drag. Calls {@link #stopDrag(boolean, boolean)} with - * true as the first argument. - */ - public void cancelDrag() { - if (mDragState == DRAGGING) { - mDragScroller.stopScrolling(true); - destroyFloatView(); - clearPositions(); - adjustAllItems(); - - if (mInTouchEvent) { - mDragState = STOPPED; - } else { - mDragState = IDLE; - } - } - } - - private void clearPositions() { - mSrcPos = -1; - mFirstExpPos = -1; - mSecondExpPos = -1; - mFloatPos = -1; - } - - private void dropFloatView() { - // must set to avoid cancelDrag being called from the - // DataSetObserver - mDragState = DROPPING; - - if (mDropListener != null && mFloatPos >= 0 && mFloatPos < getCount()) { - final int numHeaders = getHeaderViewsCount(); - mDropListener.drop(mSrcPos - numHeaders, mFloatPos - numHeaders); - } - - destroyFloatView(); - - adjustOnReorder(); - clearPositions(); - adjustAllItems(); - - // now the drag is done - if (mInTouchEvent) { - mDragState = STOPPED; - } else { - mDragState = IDLE; - } - } - - private void doRemoveItem() { - doRemoveItem(mSrcPos - getHeaderViewsCount()); - } - - /** - * Removes dragged item from the list. Calls RemoveListener. - */ - private void doRemoveItem(int which) { - // must set to avoid cancelDrag being called from the - // DataSetObserver - mDragState = REMOVING; - - // end it - if (mRemoveListener != null) { - mRemoveListener.remove(which); - } - - destroyFloatView(); - - adjustOnReorder(); - clearPositions(); - - // now the drag is done - if (mInTouchEvent) { - mDragState = STOPPED; - } else { - mDragState = IDLE; - } - } - - private void adjustOnReorder() { - final int firstPos = getFirstVisiblePosition(); - if (mSrcPos < firstPos) { - // collapsed src item is off screen; - // adjust the scroll after item heights have been fixed - View v = getChildAt(0); - int top = 0; - if (v != null) { - top = v.getTop(); - } - setSelectionFromTop(firstPos - 1, top - getPaddingTop()); - } - } - - /** - * Stop a drag in progress. Pass true if you would - * like to remove the dragged item from the list. - * - * @param remove Remove the dragged item from the list. Calls - * a registered RemoveListener, if one exists. Otherwise, calls - * the DropListener, if one exists. - * - * @return True if the stop was successful. False if there is - * no floating View. - */ - public boolean stopDrag(boolean remove) { - mUseRemoveVelocity = false; - return stopDrag(remove, 0); - } - - public boolean stopDragWithVelocity(boolean remove, float velocityX) { - - mUseRemoveVelocity = true; - return stopDrag(remove, velocityX); - } - - public boolean stopDrag(boolean remove, float velocityX) { - if (mFloatView != null) { - mDragScroller.stopScrolling(true); - - if (remove) { - removeItem(mSrcPos - getHeaderViewsCount(), velocityX); - } else { - if (mDropAnimator != null) { - mDropAnimator.start(); - } else { - dropFloatView(); - } - } - - if (mTrackDragSort) { - mDragSortTracker.stopTracking(); - } - - return true; - } else { - // stop failed - return false; - } - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - if (mIgnoreTouchEvent) { - mIgnoreTouchEvent = false; - return false; - } - - if (!mDragEnabled) { - return super.onTouchEvent(ev); - } - - boolean more = false; - - boolean lastCallWasIntercept = mLastCallWasIntercept; - mLastCallWasIntercept = false; - - if (!lastCallWasIntercept) { - saveTouchCoords(ev); - } - - // if (mFloatView != null) { - if (mDragState == DRAGGING) { - onDragTouchEvent(ev); - more = true; // give us more! - } else { - // what if float view is null b/c we dropped in middle - // of drag touch event? - - // if (mDragState != STOPPED) { - if (mDragState == IDLE) { - if (super.onTouchEvent(ev)) { - more = true; - } - } - - int action = ev.getAction() & MotionEvent.ACTION_MASK; - - switch (action) { - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - doActionUpOrCancel(); - break; - default: - if (more) { - mCancelMethod = ON_TOUCH_EVENT; - } - } - } - - return more; - } - - private void doActionUpOrCancel() { - mCancelMethod = NO_CANCEL; - mInTouchEvent = false; - if (mDragState == STOPPED) { - mDragState = IDLE; - } - mCurrFloatAlpha = mFloatAlpha; - mListViewIntercepted = false; - mChildHeightCache.clear(); - } - - private void saveTouchCoords(MotionEvent ev) { - int action = ev.getAction() & MotionEvent.ACTION_MASK; - if (action != MotionEvent.ACTION_DOWN) { - mLastX = mX; - mLastY = mY; - } - mX = (int) ev.getX(); - mY = (int) ev.getY(); - if (action == MotionEvent.ACTION_DOWN) { - mLastX = mX; - mLastY = mY; - } - mOffsetX = (int) ev.getRawX() - mX; - mOffsetY = (int) ev.getRawY() - mY; - } - - public boolean listViewIntercepted() { - return mListViewIntercepted; - } - - private boolean mListViewIntercepted = false; - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (!mDragEnabled) { - return super.onInterceptTouchEvent(ev); - } - - saveTouchCoords(ev); - mLastCallWasIntercept = true; - - int action = ev.getAction() & MotionEvent.ACTION_MASK; - - if (action == MotionEvent.ACTION_DOWN) { - if (mDragState != IDLE) { - // intercept and ignore - mIgnoreTouchEvent = true; - return true; - } - mInTouchEvent = true; - } - - boolean intercept = false; - - // the following deals with calls to super.onInterceptTouchEvent - if (mFloatView != null) { - // super's touch event canceled in startDrag - intercept = true; - } else { - if (super.onInterceptTouchEvent(ev)) { - mListViewIntercepted = true; - intercept = true; - } - - switch (action) { - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - doActionUpOrCancel(); - break; - default: - if (intercept) { - mCancelMethod = ON_TOUCH_EVENT; - } else { - mCancelMethod = ON_INTERCEPT_TOUCH_EVENT; - } - } - } - - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { - mInTouchEvent = false; - } - - return intercept; - } - - /** - * Set the width of each drag scroll region by specifying - * a fraction of the ListView height. - * - * @param heightFraction Fraction of ListView height. Capped at - * 0.5f. - * - */ - public void setDragScrollStart(float heightFraction) { - setDragScrollStarts(heightFraction, heightFraction); - } - - /** - * Set the width of each drag scroll region by specifying - * a fraction of the ListView height. - * - * @param upperFrac Fraction of ListView height for up-scroll bound. - * Capped at 0.5f. - * @param lowerFrac Fraction of ListView height for down-scroll bound. - * Capped at 0.5f. - * - */ - public void setDragScrollStarts(float upperFrac, float lowerFrac) { - if (lowerFrac > 0.5f) { - mDragDownScrollStartFrac = 0.5f; - } else { - mDragDownScrollStartFrac = lowerFrac; - } - - if (upperFrac > 0.5f) { - mDragUpScrollStartFrac = 0.5f; - } else { - mDragUpScrollStartFrac = upperFrac; - } - - if (getHeight() != 0) { - updateScrollStarts(); - } - } - - private void continueDrag(int x, int y) { - - // proposed position - mFloatLoc.x = x - mDragDeltaX; - mFloatLoc.y = y - mDragDeltaY; - - doDragFloatView(true); - - int minY = Math.min(y, mFloatViewMid + mFloatViewHeightHalf); - int maxY = Math.max(y, mFloatViewMid - mFloatViewHeightHalf); - - // get the current scroll direction - int currentScrollDir = mDragScroller.getScrollDir(); - - if (minY > mLastY && minY > mDownScrollStartY && currentScrollDir != DragScroller.DOWN) { - // dragged down, it is below the down scroll start and it is not - // scrolling up - - if (currentScrollDir != DragScroller.STOP) { - // moved directly from up scroll to down scroll - mDragScroller.stopScrolling(true); - } - - // start scrolling down - mDragScroller.startScrolling(DragScroller.DOWN); - } else if (maxY < mLastY && maxY < mUpScrollStartY && currentScrollDir != DragScroller.UP) { - // dragged up, it is above the up scroll start and it is not - // scrolling up - - if (currentScrollDir != DragScroller.STOP) { - // moved directly from down scroll to up scroll - mDragScroller.stopScrolling(true); - } - - // start scrolling up - mDragScroller.startScrolling(DragScroller.UP); - } - else if (maxY >= mUpScrollStartY && minY <= mDownScrollStartY - && mDragScroller.isScrolling()) { - // not in the upper nor in the lower drag-scroll regions but it is - // still scrolling - - mDragScroller.stopScrolling(true); - } - } - - private void updateScrollStarts() { - final int padTop = getPaddingTop(); - final int listHeight = getHeight() - padTop - getPaddingBottom(); - float heightF = (float) listHeight; - - mUpScrollStartYF = padTop + mDragUpScrollStartFrac * heightF; - mDownScrollStartYF = padTop + (1.0f - mDragDownScrollStartFrac) * heightF; - - mUpScrollStartY = (int) mUpScrollStartYF; - mDownScrollStartY = (int) mDownScrollStartYF; - - mDragUpScrollHeight = mUpScrollStartYF - padTop; - mDragDownScrollHeight = padTop + listHeight - mDownScrollStartYF; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - updateScrollStarts(); - } - - private void adjustAllItems() { - final int first = getFirstVisiblePosition(); - final int last = getLastVisiblePosition(); - - int begin = Math.max(0, getHeaderViewsCount() - first); - int end = Math.min(last - first, getCount() - 1 - getFooterViewsCount() - first); - - for (int i = begin; i <= end; ++i) { - View v = getChildAt(i); - if (v != null) { - adjustItem(first + i, v, false); - } - } - } - - private void adjustItem(int position) { - View v = getChildAt(position - getFirstVisiblePosition()); - - if (v != null) { - adjustItem(position, v, false); - } - } - - /** - * Sets layout param height, gravity, and visibility on - * wrapped item. - */ - private void adjustItem(int position, View v, boolean invalidChildHeight) { - - // Adjust item height - ViewGroup.LayoutParams lp = v.getLayoutParams(); - int height; - if (position != mSrcPos && position != mFirstExpPos && position != mSecondExpPos) { - height = ViewGroup.LayoutParams.WRAP_CONTENT; - } else { - height = calcItemHeight(position, v, invalidChildHeight); - } - - if (height != lp.height) { - lp.height = height; - v.setLayoutParams(lp); - } - - // Adjust item gravity - if (position == mFirstExpPos || position == mSecondExpPos) { - if (position < mSrcPos) { - ((DragSortItemView) v).setGravity(Gravity.BOTTOM); - } else if (position > mSrcPos) { - ((DragSortItemView) v).setGravity(Gravity.TOP); - } - } - - // Finally adjust item visibility - - int oldVis = v.getVisibility(); - int vis = View.VISIBLE; - - if (position == mSrcPos && mFloatView != null) { - vis = View.INVISIBLE; - } - - if (vis != oldVis) { - v.setVisibility(vis); - } - } - - private int getChildHeight(int position) { - if (position == mSrcPos) { - return 0; - } - - View v = getChildAt(position - getFirstVisiblePosition()); - - if (v != null) { - // item is onscreen, therefore child height is valid, - // hence the "true" - return getChildHeight(position, v, false); - } else { - // item is offscreen - // first check cache for child height at this position - int childHeight = mChildHeightCache.get(position); - if (childHeight != -1) { - return childHeight; - } - - final ListAdapter adapter = getAdapter(); - int type = adapter.getItemViewType(position); - - // There might be a better place for checking for the following - final int typeCount = adapter.getViewTypeCount(); - if (typeCount != mSampleViewTypes.length) { - mSampleViewTypes = new View[typeCount]; - } - - if (type >= 0) { - if (mSampleViewTypes[type] == null) { - v = adapter.getView(position, null, this); - mSampleViewTypes[type] = v; - } else { - v = adapter.getView(position, mSampleViewTypes[type], this); - } - } else { - // type is HEADER_OR_FOOTER or IGNORE - v = adapter.getView(position, null, this); - } - - // current child height is invalid, hence "true" below - childHeight = getChildHeight(position, v, true); - - // cache it because this could have been expensive - mChildHeightCache.add(position, childHeight); - - return childHeight; - } - } - - private int getChildHeight(int position, View item, boolean invalidChildHeight) { - if (position == mSrcPos) { - return 0; - } - - View child; - if (position < getHeaderViewsCount() || position >= getCount() - getFooterViewsCount()) { - child = item; - } else { - child = ((ViewGroup) item).getChildAt(0); - } - - ViewGroup.LayoutParams lp = child.getLayoutParams(); - - if (lp != null) { - if (lp.height > 0) { - return lp.height; - } - } - - int childHeight = child.getHeight(); - - if (childHeight == 0 || invalidChildHeight) { - measureItem(child); - childHeight = child.getMeasuredHeight(); - } - - return childHeight; - } - - private int calcItemHeight(int position, View item, boolean invalidChildHeight) { - return calcItemHeight(position, getChildHeight(position, item, invalidChildHeight)); - } - - private int calcItemHeight(int position, int childHeight) { - - int divHeight = getDividerHeight(); - - boolean isSliding = mAnimate && mFirstExpPos != mSecondExpPos; - int maxNonSrcBlankHeight = mFloatViewHeight - mItemHeightCollapsed; - int slideHeight = (int) (mSlideFrac * maxNonSrcBlankHeight); - - int height; - - if (position == mSrcPos) { - if (mSrcPos == mFirstExpPos) { - if (isSliding) { - height = slideHeight + mItemHeightCollapsed; - } else { - height = mFloatViewHeight; - } - } else if (mSrcPos == mSecondExpPos) { - // if gets here, we know an item is sliding - height = mFloatViewHeight - slideHeight; - } else { - height = mItemHeightCollapsed; - } - } else if (position == mFirstExpPos) { - if (isSliding) { - height = childHeight + slideHeight; - } else { - height = childHeight + maxNonSrcBlankHeight; - } - } else if (position == mSecondExpPos) { - // we know an item is sliding (b/c 2ndPos != 1stPos) - height = childHeight + maxNonSrcBlankHeight - slideHeight; - } else { - height = childHeight; - } - - return height; - } - - @Override - public void requestLayout() { - if (!mBlockLayoutRequests) { - super.requestLayout(); - } - } - - private int adjustScroll(int movePos, View moveItem, int oldFirstExpPos, int oldSecondExpPos) { - int adjust = 0; - - final int childHeight = getChildHeight(movePos); - - int moveHeightBefore = moveItem.getHeight(); - int moveHeightAfter = calcItemHeight(movePos, childHeight); - - int moveBlankBefore = moveHeightBefore; - int moveBlankAfter = moveHeightAfter; - if (movePos != mSrcPos) { - moveBlankBefore -= childHeight; - moveBlankAfter -= childHeight; - } - - int maxBlank = mFloatViewHeight; - if (mSrcPos != mFirstExpPos && mSrcPos != mSecondExpPos) { - maxBlank -= mItemHeightCollapsed; - } - - if (movePos <= oldFirstExpPos) { - if (movePos > mFirstExpPos) { - adjust += maxBlank - moveBlankAfter; - } - } else if (movePos == oldSecondExpPos) { - if (movePos <= mFirstExpPos) { - adjust += moveBlankBefore - maxBlank; - } else if (movePos == mSecondExpPos) { - adjust += moveHeightBefore - moveHeightAfter; - } else { - adjust += moveBlankBefore; - } - } else { - if (movePos <= mFirstExpPos) { - adjust -= maxBlank; - } else if (movePos == mSecondExpPos) { - adjust -= moveBlankAfter; - } - } - - return adjust; - } - - private void measureItem(View item) { - ViewGroup.LayoutParams lp = item.getLayoutParams(); - if (lp == null) { - lp = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - item.setLayoutParams(lp); - } - int wspec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, getListPaddingLeft() - + getListPaddingRight(), lp.width); - int hspec; - if (lp.height > 0) { - hspec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY); - } else { - hspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - } - item.measure(wspec, hspec); - } - - private void measureFloatView() { - if (mFloatView != null) { - measureItem(mFloatView); - mFloatViewHeight = mFloatView.getMeasuredHeight(); - mFloatViewHeightHalf = mFloatViewHeight / 2; - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mFloatView != null) { - if (mFloatView.isLayoutRequested()) { - measureFloatView(); - } - mFloatViewOnMeasured = true; // set to false after layout - } - mWidthMeasureSpec = widthMeasureSpec; - } - - @Override - protected void layoutChildren() { - super.layoutChildren(); - - if (mFloatView != null) { - if (mFloatView.isLayoutRequested() && !mFloatViewOnMeasured) { - // Have to measure here when usual android measure - // pass is skipped. This happens during a drag-sort - // when layoutChildren is called directly. - measureFloatView(); - } - mFloatView.layout(0, 0, mFloatView.getMeasuredWidth(), mFloatView.getMeasuredHeight()); - mFloatViewOnMeasured = false; - } - } - - protected boolean onDragTouchEvent(MotionEvent ev) { - // we are in a drag - int action = ev.getAction() & MotionEvent.ACTION_MASK; - - switch (ev.getAction() & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_CANCEL: - if (mDragState == DRAGGING) { - cancelDrag(); - } - doActionUpOrCancel(); - break; - case MotionEvent.ACTION_UP: - if (mDragState == DRAGGING) { - stopDrag(false); - } - doActionUpOrCancel(); - break; - case MotionEvent.ACTION_MOVE: - continueDrag((int) ev.getX(), (int) ev.getY()); - break; - } - - return true; - } - - private boolean mFloatViewInvalidated = false; - - private void invalidateFloatView() { - mFloatViewInvalidated = true; - } - - /** - * Start a drag of item at position using the - * registered FloatViewManager. Calls through - * to {@link #startDrag(int,View,int,int,int)} after obtaining - * the floating View from the FloatViewManager. - * - * @param position Item to drag. - * @param dragFlags Flags that restrict some movements of the - * floating View. For example, set dragFlags |= - * ~{@link #DRAG_NEG_X} to allow dragging the floating - * View in all directions except off the screen to the left. - * @param deltaX Offset in x of the touch coordinate from the - * left edge of the floating View (i.e. touch-x minus float View - * left). - * @param deltaY Offset in y of the touch coordinate from the - * top edge of the floating View (i.e. touch-y minus float View - * top). - * - * @return True if the drag was started, false otherwise. This - * startDrag will fail if we are not currently in - * a touch event, there is no registered FloatViewManager, - * or the FloatViewManager returns a null View. - */ - public boolean startDrag(int position, int dragFlags, int deltaX, int deltaY) { - if (!mInTouchEvent || mFloatViewManager == null) { - return false; - } - - View v = mFloatViewManager.onCreateFloatView(position); - - if (v == null) { - return false; - } else { - return startDrag(position, v, dragFlags, deltaX, deltaY); - } - - } - - /** - * Start a drag of item at position without using - * a FloatViewManager. - * - * @param position Item to drag. - * @param floatView Floating View. - * @param dragFlags Flags that restrict some movements of the - * floating View. For example, set dragFlags |= - * ~{@link #DRAG_NEG_X} to allow dragging the floating - * View in all directions except off the screen to the left. - * @param deltaX Offset in x of the touch coordinate from the - * left edge of the floating View (i.e. touch-x minus float View - * left). - * @param deltaY Offset in y of the touch coordinate from the - * top edge of the floating View (i.e. touch-y minus float View - * top). - * - * @return True if the drag was started, false otherwise. This - * startDrag will fail if we are not currently in - * a touch event, floatView is null, or there is - * a drag in progress. - */ - public boolean startDrag(int position, View floatView, int dragFlags, int deltaX, int deltaY) { - if (mDragState != IDLE || !mInTouchEvent || mFloatView != null || floatView == null - || !mDragEnabled) { - return false; - } - - if (getParent() != null) { - getParent().requestDisallowInterceptTouchEvent(true); - } - - int pos = position + getHeaderViewsCount(); - mFirstExpPos = pos; - mSecondExpPos = pos; - mSrcPos = pos; - mFloatPos = pos; - - // mDragState = dragType; - mDragState = DRAGGING; - mDragFlags = 0; - mDragFlags |= dragFlags; - - mFloatView = floatView; - measureFloatView(); // sets mFloatViewHeight - - mDragDeltaX = deltaX; - mDragDeltaY = deltaY; - mDragStartY = mY; - - // updateFloatView(mX - mDragDeltaX, mY - mDragDeltaY); - mFloatLoc.x = mX - mDragDeltaX; - mFloatLoc.y = mY - mDragDeltaY; - - // set src item invisible - final View srcItem = getChildAt(mSrcPos - getFirstVisiblePosition()); - - if (srcItem != null) { - srcItem.setVisibility(View.INVISIBLE); - } - - if (mTrackDragSort) { - mDragSortTracker.startTracking(); - } - - // once float view is created, events are no longer passed - // to ListView - switch (mCancelMethod) { - case ON_TOUCH_EVENT: - super.onTouchEvent(mCancelEvent); - break; - case ON_INTERCEPT_TOUCH_EVENT: - super.onInterceptTouchEvent(mCancelEvent); - break; - } - - requestLayout(); - - if (mLiftAnimator != null) { - mLiftAnimator.start(); - } - - return true; - } - - private void doDragFloatView(boolean forceInvalidate) { - int movePos = getFirstVisiblePosition() + getChildCount() / 2; - View moveItem = getChildAt(getChildCount() / 2); - - if (moveItem == null) { - return; - } - - doDragFloatView(movePos, moveItem, forceInvalidate); - } - - private void doDragFloatView(int movePos, View moveItem, boolean forceInvalidate) { - mBlockLayoutRequests = true; - - updateFloatView(); - - int oldFirstExpPos = mFirstExpPos; - int oldSecondExpPos = mSecondExpPos; - - boolean updated = updatePositions(); - - if (updated) { - adjustAllItems(); - int scroll = adjustScroll(movePos, moveItem, oldFirstExpPos, oldSecondExpPos); - - setSelectionFromTop(movePos, moveItem.getTop() + scroll - getPaddingTop()); - layoutChildren(); - } - - if (updated || forceInvalidate) { - invalidate(); - } - - mBlockLayoutRequests = false; - } - - /** - * Sets float View location based on suggested values and - * constraints set in mDragFlags. - */ - private void updateFloatView() { - - if (mFloatViewManager != null) { - mTouchLoc.set(mX, mY); - mFloatViewManager.onDragFloatView(mFloatView, mFloatLoc, mTouchLoc); - } - - final int floatX = mFloatLoc.x; - final int floatY = mFloatLoc.y; - - // restrict x motion - int padLeft = getPaddingLeft(); - if ((mDragFlags & DRAG_POS_X) == 0 && floatX > padLeft) { - mFloatLoc.x = padLeft; - } else if ((mDragFlags & DRAG_NEG_X) == 0 && floatX < padLeft) { - mFloatLoc.x = padLeft; - } - - // keep floating view from going past bottom of last header view - final int numHeaders = getHeaderViewsCount(); - final int numFooters = getFooterViewsCount(); - final int firstPos = getFirstVisiblePosition(); - final int lastPos = getLastVisiblePosition(); - - // "nHead="+numHeaders+" nFoot="+numFooters+" first="+firstPos+" last="+lastPos); - int topLimit = getPaddingTop(); - if (firstPos < numHeaders) { - topLimit = getChildAt(numHeaders - firstPos - 1).getBottom(); - } - if ((mDragFlags & DRAG_NEG_Y) == 0) { - if (firstPos <= mSrcPos) { - topLimit = Math.max(getChildAt(mSrcPos - firstPos).getTop(), topLimit); - } - } - // bottom limit is top of first footer View or - // bottom of last item in list - int bottomLimit = getHeight() - getPaddingBottom(); - if (lastPos >= getCount() - numFooters - 1) { - bottomLimit = getChildAt(getCount() - numFooters - 1 - firstPos).getBottom(); - } - if ((mDragFlags & DRAG_POS_Y) == 0) { - if (lastPos >= mSrcPos) { - bottomLimit = Math.min(getChildAt(mSrcPos - firstPos).getBottom(), bottomLimit); - } - } - - if (floatY < topLimit) { - mFloatLoc.y = topLimit; - } else if (floatY + mFloatViewHeight > bottomLimit) { - mFloatLoc.y = bottomLimit - mFloatViewHeight; - } - - // get y-midpoint of floating view (constrained to ListView bounds) - mFloatViewMid = mFloatLoc.y + mFloatViewHeightHalf; - } - - private void destroyFloatView() { - if (mFloatView != null) { - mFloatView.setVisibility(GONE); - if (mFloatViewManager != null) { - mFloatViewManager.onDestroyFloatView(mFloatView); - } - mFloatView = null; - invalidate(); - } - } - - /** - * Interface for customization of the floating View appearance - * and dragging behavior. Implement - * your own and pass it to {@link #setFloatViewManager}. If - * your own is not passed, the default {@link SimpleFloatViewManager} - * implementation is used. - */ - public interface FloatViewManager { - /** - * Return the floating View for item at position. - * DragSortListView will measure and layout this View for you, - * so feel free to just inflate it. You can help DSLV by - * setting some {@link ViewGroup.LayoutParams} on this View; - * otherwise it will set some for you (with a width of FILL_PARENT - * and a height of WRAP_CONTENT). - * - * @param position Position of item to drag (NOTE: - * position excludes header Views; thus, if you - * want to call {@link ListView#getChildAt(int)}, you will need - * to add {@link ListView#getHeaderViewsCount()} to the index). - * - * @return The View you wish to display as the floating View. - */ - public View onCreateFloatView(int position); - - /** - * Called whenever the floating View is dragged. Float View - * properties can be changed here. Also, the upcoming location - * of the float View can be altered by setting - * location.x and location.y. - * - * @param floatView The floating View. - * @param location The location (top-left; relative to DSLV - * top-left) at which the float - * View would like to appear, given the current touch location - * and the offset provided in {@link DragSortListView#startDrag}. - * @param touch The current touch location (relative to DSLV - * top-left). - * @param pendingScroll - */ - public void onDragFloatView(View floatView, Point location, Point touch); - - /** - * Called when the float View is dropped; lets you perform - * any necessary cleanup. The internal DSLV floating View - * reference is set to null immediately after this is called. - * - * @param floatView The floating View passed to - * {@link #onCreateFloatView(int)}. - */ - public void onDestroyFloatView(View floatView); - } - - public void setFloatViewManager(FloatViewManager manager) { - mFloatViewManager = manager; - } - - public void setDragListener(DragListener l) { - mDragListener = l; - } - - /** - * Allows for easy toggling between a DragSortListView - * and a regular old ListView. If enabled, items are - * draggable, where the drag init mode determines how - * items are lifted (see {@link setDragInitMode(int)}). - * If disabled, items cannot be dragged. - * - * @param enabled Set true to enable list - * item dragging - */ - public void setDragEnabled(boolean enabled) { - mDragEnabled = enabled; - } - - public boolean isDragEnabled() { - return mDragEnabled; - } - - /** - * This better reorder your ListAdapter! DragSortListView does not do this - * for you; doesn't make sense to. Make sure - * {@link BaseAdapter#notifyDataSetChanged()} or something like it is called - * in your implementation. Furthermore, if you have a choiceMode other than - * none and the ListAdapter does not return true for - * {@link ListAdapter#hasStableIds()}, you will need to call - * {@link #moveCheckState(int, int)} to move the check boxes along with the - * list items. - * - * @param l - */ - public void setDropListener(DropListener l) { - mDropListener = l; - } - - /** - * Probably a no-brainer, but make sure that your remove listener - * calls {@link BaseAdapter#notifyDataSetChanged()} or something like it. - * When an item removal occurs, DragSortListView - * relies on a redraw of all the items to recover invisible views - * and such. Strictly speaking, if you remove something, your dataset - * has changed... - * - * @param l - */ - public void setRemoveListener(RemoveListener l) { - mRemoveListener = l; - } - - public interface DragListener { - public void drag(int from, int to); - } - - /** - * Your implementation of this has to reorder your ListAdapter! - * Make sure to call - * {@link BaseAdapter#notifyDataSetChanged()} or something like it - * in your implementation. - * - * @author heycosmo - * - */ - public interface DropListener { - public void drop(int from, int to); - } - - /** - * Make sure to call - * {@link BaseAdapter#notifyDataSetChanged()} or something like it - * in your implementation. - * - * @author heycosmo - * - */ - public interface RemoveListener { - public void remove(int which); - } - - public interface DragSortListener extends DropListener, DragListener, RemoveListener { - } - - public void setDragSortListener(DragSortListener l) { - setDropListener(l); - setDragListener(l); - setRemoveListener(l); - } - - /** - * Completely custom scroll speed profile. Default increases linearly - * with position and is constant in time. Create your own by implementing - * {@link DragSortListView.DragScrollProfile}. - * - * @param ssp - */ - public void setDragScrollProfile(DragScrollProfile ssp) { - if (ssp != null) { - mScrollProfile = ssp; - } - } - - /** - * Use this to move the check state of an item from one position to another - * in a drop operation. If you have a choiceMode which is not none, this - * method must be called when the order of items changes in an underlying - * adapter which does not have stable IDs (see - * {@link ListAdapter#hasStableIds()}). This is because without IDs, the - * ListView has no way of knowing which items have moved where, and cannot - * update the check state accordingly. - *

- * A word of warning about a "feature" in Android that you may run into when - * dealing with movable list items: for an adapter that does have - * stable IDs, ListView will attempt to locate each item based on its ID and - * move the check state from the item's old position to the new position — - * which is all fine and good (and removes the need for calling this - * function), except for the half-baked approach. Apparently to save time in - * the naive algorithm used, ListView will only search for an ID in the - * close neighborhood of the old position. If the user moves an item too far - * (specifically, more than 20 rows away), ListView will give up and just - * force the item to be unchecked. So if there is a reasonable chance that - * the user will move items more than 20 rows away from the original - * position, you may wish to use an adapter with unstable IDs and call this - * method manually instead. - * - * @param from - * @param to - */ - public void moveCheckState(int from, int to) { - // This method runs in O(n log n) time (n being the number of list - // items). The bottleneck is the call to AbsListView.setItemChecked, - // which is O(log n) because of the binary search involved in calling - // SparseBooleanArray.put(). - // - // To improve on the average time, we minimize the number of calls to - // setItemChecked by only calling it for items that actually have a - // changed state. This is achieved by building a list containing the - // start and end of the "runs" of checked items, and then moving the - // runs. Note that moving an item from A to B is essentially a rotation - // of the range of items in [A, B]. Let's say we have - // . . U V X Y Z . . - // and move U after Z. This is equivalent to a rotation one step to the - // left within the range you are moving across: - // . . V X Y Z U . . - // - // So, to perform the move we enumerate all the runs within the move - // range, then rotate each run one step to the left or right (depending - // on move direction). For example, in the list: - // X X . X X X . X - // we have two runs. One begins at the last item of the list and wraps - // around to the beginning, ending at position 1. The second begins at - // position 3 and ends at position 5. To rotate a run, regardless of - // length, we only need to set a check mark at one end of the run, and - // clear a check mark at the other end: - // X . X X X . X X - SparseBooleanArray cip = getCheckedItemPositions(); - int rangeStart = from; - int rangeEnd = to; - if (to < from) { - rangeStart = to; - rangeEnd = from; - } - rangeEnd += 1; - - int[] runStart = new int[cip.size()]; - int[] runEnd = new int[cip.size()]; - int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd); - if (runCount == 1 && (runStart[0] == runEnd[0])) { - // Special case where all items are checked, we can never set any - // item to false like we do below. - return; - } - - if (from < to) { - for (int i = 0; i != runCount; i++) { - setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true); - setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false); - } - - } else { - for (int i = 0; i != runCount; i++) { - setItemChecked(runStart[i], false); - setItemChecked(runEnd[i], true); - } - } - } - - /** - * Use this when an item has been deleted, to move the check state of all - * following items up one step. If you have a choiceMode which is not none, - * this method must be called when the order of items changes in an - * underlying adapter which does not have stable IDs (see - * {@link ListAdapter#hasStableIds()}). This is because without IDs, the - * ListView has no way of knowing which items have moved where, and cannot - * update the check state accordingly. - * - * See also further comments on {@link #moveCheckState(int, int)}. - * - * @param position - */ - public void removeCheckState(int position) { - SparseBooleanArray cip = getCheckedItemPositions(); - - if (cip.size() == 0) - return; - int[] runStart = new int[cip.size()]; - int[] runEnd = new int[cip.size()]; - int rangeStart = position; - int rangeEnd = cip.keyAt(cip.size() - 1) + 1; - int runCount = buildRunList(cip, rangeStart, rangeEnd, runStart, runEnd); - for (int i = 0; i != runCount; i++) { - if (!(runStart[i] == position || (runEnd[i] < runStart[i] && runEnd[i] > position))) { - // Only set a new check mark in front of this run if it does - // not contain the deleted position. If it does, we only need - // to make it one check mark shorter at the end. - setItemChecked(rotate(runStart[i], -1, rangeStart, rangeEnd), true); - } - setItemChecked(rotate(runEnd[i], -1, rangeStart, rangeEnd), false); - } - } - - private static int buildRunList(SparseBooleanArray cip, int rangeStart, - int rangeEnd, int[] runStart, int[] runEnd) { - int runCount = 0; - - int i = findFirstSetIndex(cip, rangeStart, rangeEnd); - if (i == -1) - return 0; - - int position = cip.keyAt(i); - int currentRunStart = position; - int currentRunEnd = currentRunStart + 1; - for (i++; i < cip.size() && (position = cip.keyAt(i)) < rangeEnd; i++) { - if (!cip.valueAt(i)) // not checked => not interesting - continue; - if (position == currentRunEnd) { - currentRunEnd++; - } else { - runStart[runCount] = currentRunStart; - runEnd[runCount] = currentRunEnd; - runCount++; - currentRunStart = position; - currentRunEnd = position + 1; - } - } - - if (currentRunEnd == rangeEnd) { - // rangeStart and rangeEnd are equivalent positions so to be - // consistent we translate them to the same integer value. That way - // we can check whether a run covers the entire range by just - // checking if the start equals the end position. - currentRunEnd = rangeStart; - } - runStart[runCount] = currentRunStart; - runEnd[runCount] = currentRunEnd; - runCount++; - - if (runCount > 1) { - if (runStart[0] == rangeStart && runEnd[runCount - 1] == rangeStart) { - // The last run ends at the end of the range, and the first run - // starts at the beginning of the range. So they are actually - // part of the same run, except they wrap around the end of the - // range. To avoid adjacent runs, we need to merge them. - runStart[0] = runStart[runCount - 1]; - runCount--; - } - } - return runCount; - } - - private static int rotate(int value, int offset, int lowerBound, int upperBound) { - int windowSize = upperBound - lowerBound; - - value += offset; - if (value < lowerBound) { - value += windowSize; - } else if (value >= upperBound) { - value -= windowSize; - } - return value; - } - - private static int findFirstSetIndex(SparseBooleanArray sba, int rangeStart, int rangeEnd) { - int size = sba.size(); - int i = insertionIndexForKey(sba, rangeStart); - while (i < size && sba.keyAt(i) < rangeEnd && !sba.valueAt(i)) - i++; - if (i == size || sba.keyAt(i) >= rangeEnd) - return -1; - return i; - } - - private static int insertionIndexForKey(SparseBooleanArray sba, int key) { - int low = 0; - int high = sba.size(); - while (high - low > 0) { - int middle = (low + high) >> 1; - if (sba.keyAt(middle) < key) - low = middle + 1; - else - high = middle; - } - return low; - } - - /** - * Interface for controlling - * scroll speed as a function of touch position and time. Use - * {@link DragSortListView#setDragScrollProfile(DragScrollProfile)} to - * set custom profile. - * - * @author heycosmo - * - */ - public interface DragScrollProfile { - /** - * Return a scroll speed in pixels/millisecond. Always return a - * positive number. - * - * @param w Normalized position in scroll region (i.e. w \in [0,1]). - * Small w typically means slow scrolling. - * @param t Time (in milliseconds) since start of scroll (handy if you - * want scroll acceleration). - * @return Scroll speed at position w and time t in pixels/ms. - */ - float getSpeed(float w, long t); - } - - private class DragScroller implements Runnable { - - private boolean mAbort; - - private long mPrevTime; - private long mCurrTime; - - private int dy; - private float dt; - private long tStart; - private int scrollDir; - - public final static int STOP = -1; - public final static int UP = 0; - public final static int DOWN = 1; - - private float mScrollSpeed; // pixels per ms - - private boolean mScrolling = false; - - private int mLastHeader; - private int mFirstFooter; - - public boolean isScrolling() { - return mScrolling; - } - - public int getScrollDir() { - return mScrolling ? scrollDir : STOP; - } - - public DragScroller() { - } - - public void startScrolling(int dir) { - if (!mScrolling) { - // Debug.startMethodTracing("dslv-scroll"); - mAbort = false; - mScrolling = true; - tStart = SystemClock.uptimeMillis(); - mPrevTime = tStart; - scrollDir = dir; - post(this); - } - } - - public void stopScrolling(boolean now) { - if (now) { - DragSortListView.this.removeCallbacks(this); - mScrolling = false; - } else { - mAbort = true; - } - - // Debug.stopMethodTracing(); - } - - @Override - public void run() { - if (mAbort) { - mScrolling = false; - return; - } - - - final int first = getFirstVisiblePosition(); - final int last = getLastVisiblePosition(); - final int count = getCount(); - final int padTop = getPaddingTop(); - final int listHeight = getHeight() - padTop - getPaddingBottom(); - - int minY = Math.min(mY, mFloatViewMid + mFloatViewHeightHalf); - int maxY = Math.max(mY, mFloatViewMid - mFloatViewHeightHalf); - - if (scrollDir == UP) { - View v = getChildAt(0); - if (v == null) { - mScrolling = false; - return; - } else { - if (first == 0 && v.getTop() == padTop) { - mScrolling = false; - return; - } - } - mScrollSpeed = mScrollProfile.getSpeed((mUpScrollStartYF - maxY) - / mDragUpScrollHeight, mPrevTime); - } else { - View v = getChildAt(last - first); - if (v == null) { - mScrolling = false; - return; - } else { - if (last == count - 1 && v.getBottom() <= listHeight + padTop) { - mScrolling = false; - return; - } - } - mScrollSpeed = -mScrollProfile.getSpeed((minY - mDownScrollStartYF) - / mDragDownScrollHeight, mPrevTime); - } - - mCurrTime = SystemClock.uptimeMillis(); - dt = (float) (mCurrTime - mPrevTime); - - // dy is change in View position of a list item; i.e. positive dy - // means user is scrolling up (list item moves down the screen, - // remember - // y=0 is at top of View). - dy = (int) Math.round(mScrollSpeed * dt); - - int movePos; - if (dy >= 0) { - dy = Math.min(listHeight, dy); - movePos = first; - } else { - dy = Math.max(-listHeight, dy); - movePos = last; - } - - final View moveItem = getChildAt(movePos - first); - int top = moveItem.getTop() + dy; - - if (movePos == 0 && top > padTop) { - top = padTop; - } - - // always do scroll - mBlockLayoutRequests = true; - - // This cast is a workaround of an API bug, see https://issuetracker.google.com/issues/37045361 - ((ListView)DragSortListView.this).setSelectionFromTop(movePos, top - padTop); - - DragSortListView.this.layoutChildren(); - invalidate(); - - mBlockLayoutRequests = false; - - // scroll means relative float View movement - doDragFloatView(movePos, moveItem, false); - - mPrevTime = mCurrTime; - post(this); - } - } - - private class DragSortTracker { - StringBuilder mBuilder = new StringBuilder(); - - File mFile; - - private int mNumInBuffer = 0; - private int mNumFlushes = 0; - - private boolean mTracking = false; - - public DragSortTracker() { - File root = Environment.getExternalStorageDirectory(); - mFile = new File(root, "dslv_state.txt"); - - if (!mFile.exists()) { - try { - mFile.createNewFile(); - Timber.d("file created"); - } catch (IOException e) { - Timber.w("Could not create dslv_state.txt"); - Timber.d(e.getMessage()); - } - } - - } - - public void startTracking() { - mBuilder.append("\n"); - mNumFlushes = 0; - mTracking = true; - } - - public void appendState() { - if (!mTracking) { - return; - } - - mBuilder.append("\n"); - final int children = getChildCount(); - final int first = getFirstVisiblePosition(); - mBuilder.append(" "); - for (int i = 0; i < children; ++i) { - mBuilder.append(first + i).append(","); - } - mBuilder.append("\n"); - - mBuilder.append(" "); - for (int i = 0; i < children; ++i) { - mBuilder.append(getChildAt(i).getTop()).append(","); - } - mBuilder.append("\n"); - mBuilder.append(" "); - for (int i = 0; i < children; ++i) { - mBuilder.append(getChildAt(i).getBottom()).append(","); - } - mBuilder.append("\n"); - - mBuilder.append(" ").append(mFirstExpPos).append("\n"); - mBuilder.append(" ") - .append(getItemHeight(mFirstExpPos) - getChildHeight(mFirstExpPos)) - .append("\n"); - mBuilder.append(" ").append(mSecondExpPos).append("\n"); - mBuilder.append(" ") - .append(getItemHeight(mSecondExpPos) - getChildHeight(mSecondExpPos)) - .append("\n"); - mBuilder.append(" ").append(mSrcPos).append("\n"); - mBuilder.append(" ").append(mFloatViewHeight + getDividerHeight()) - .append("\n"); - mBuilder.append(" ").append(getHeight()).append("\n"); - mBuilder.append(" ").append(mLastY).append("\n"); - mBuilder.append(" ").append(mFloatViewMid).append("\n"); - mBuilder.append(" "); - for (int i = 0; i < children; ++i) { - mBuilder.append(getShuffleEdge(first + i, getChildAt(i).getTop())).append(","); - } - mBuilder.append("\n"); - - mBuilder.append("\n"); - mNumInBuffer++; - - if (mNumInBuffer > 1000) { - flush(); - mNumInBuffer = 0; - } - } - - public void flush() { - if (!mTracking) { - return; - } - - // save to file on sdcard - try { - boolean append = true; - if (mNumFlushes == 0) { - append = false; - } - FileWriter writer = new FileWriter(mFile, append); - - writer.write(mBuilder.toString()); - mBuilder.delete(0, mBuilder.length()); - - writer.flush(); - writer.close(); - - mNumFlushes++; - } catch (IOException e) { - // do nothing - } - } - - public void stopTracking() { - if (mTracking) { - mBuilder.append("\n"); - flush(); - mTracking = false; - } - } - - } - -} diff --git a/core/library/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java b/core/library/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java deleted file mode 100644 index f2d08107..00000000 --- a/core/library/src/main/java/com/mobeta/android/dslv/ResourceDragSortCursorAdapter.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2011 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 com.mobeta.android.dslv; - -import android.content.Context; -import android.database.Cursor; -import android.view.View; -import android.view.ViewGroup; -import android.view.LayoutInflater; - -// taken from v4 rev. 10 ResourceCursorAdapter.java - -/** - * Static library support version of the framework's {@link android.widget.ResourceCursorAdapter}. - * Used to write apps that run on platforms prior to Android 3.0. When running - * on Android 3.0 or above, this implementation is still used; it does not try - * to switch to the framework's implementation. See the framework SDK - * documentation for a class overview. - */ -public abstract class ResourceDragSortCursorAdapter extends DragSortCursorAdapter { - private int mLayout; - - private int mDropDownLayout; - - private LayoutInflater mInflater; - - /** - * Constructor the enables auto-requery. - * - * @deprecated This option is discouraged, as it results in Cursor queries - * being performed on the application's UI thread and thus can cause poor - * responsiveness or even Application Not Responding errors. As an alternative, - * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}. - * - * @param context The context where the ListView associated with this adapter is running - * @param layout resource identifier of a layout file that defines the views - * for this list item. Unless you override them later, this will - * define both the item views and the drop down views. - */ - @Deprecated - public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c) { - super(context, c); - mLayout = mDropDownLayout = layout; - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - /** - * Constructor with default behavior as per - * {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended - * you not use this, but instead {@link #ResourceCursorAdapter(Context, int, Cursor, int)}. - * When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER} - * will always be set. - * - * @param context The context where the ListView associated with this adapter is running - * @param layout resource identifier of a layout file that defines the views - * for this list item. Unless you override them later, this will - * define both the item views and the drop down views. - * @param c The cursor from which to get the data. - * @param autoRequery If true the adapter will call requery() on the - * cursor whenever it changes so the most recent - * data is always displayed. Using true here is discouraged. - */ - public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) { - super(context, c, autoRequery); - mLayout = mDropDownLayout = layout; - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - /** - * Standard constructor. - * - * @param context The context where the ListView associated with this adapter is running - * @param layout Resource identifier of a layout file that defines the views - * for this list item. Unless you override them later, this will - * define both the item views and the drop down views. - * @param c The cursor from which to get the data. - * @param flags Flags used to determine the behavior of the adapter, - * as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}. - */ - public ResourceDragSortCursorAdapter(Context context, int layout, Cursor c, int flags) { - super(context, c, flags); - mLayout = mDropDownLayout = layout; - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - - /** - * Inflates view(s) from the specified XML file. - * - * @see android.widget.CursorAdapter#newView(android.content.Context, - * android.database.Cursor, ViewGroup) - */ - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(mLayout, parent, false); - } - - @Override - public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(mDropDownLayout, parent, false); - } - - /** - *

Sets the layout resource of the item views.

- * - * @param layout the layout resources used to create item views - */ - public void setViewResource(int layout) { - mLayout = layout; - } - - /** - *

Sets the layout resource of the drop down views.

- * - * @param dropDownLayout the layout resources used to create drop down views - */ - public void setDropDownViewResource(int dropDownLayout) { - mDropDownLayout = dropDownLayout; - } -} diff --git a/core/library/src/main/java/com/mobeta/android/dslv/SimpleDragSortCursorAdapter.java b/core/library/src/main/java/com/mobeta/android/dslv/SimpleDragSortCursorAdapter.java deleted file mode 100644 index 7a76ea9d..00000000 --- a/core/library/src/main/java/com/mobeta/android/dslv/SimpleDragSortCursorAdapter.java +++ /dev/null @@ -1,422 +0,0 @@ -/* - * Copyright (C) 2006 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 com.mobeta.android.dslv; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.view.View; -import android.widget.TextView; -import android.widget.ImageView; - -// taken from sdk/sources/android-16/android/widget/SimpleCursorAdapter.java - -/** - * An easy adapter to map columns from a cursor to TextViews or ImageViews - * defined in an XML file. You can specify which columns you want, which - * views you want to display the columns, and the XML file that defines - * the appearance of these views. - * - * Binding occurs in two phases. First, if a - * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available, - * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)} - * is invoked. If the returned value is true, binding has occured. If the - * returned value is false and the view to bind is a TextView, - * {@link #setViewText(TextView, String)} is invoked. If the returned value - * is false and the view to bind is an ImageView, - * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate - * binding can be found, an {@link IllegalStateException} is thrown. - * - * If this adapter is used with filtering, for instance in an - * {@link android.widget.AutoCompleteTextView}, you can use the - * {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the - * {@link android.widget.FilterQueryProvider} interfaces - * to get control over the filtering process. You can refer to - * {@link #convertToString(android.database.Cursor)} and - * {@link #runQueryOnBackgroundThread(CharSequence)} for more information. - */ -public class SimpleDragSortCursorAdapter extends ResourceDragSortCursorAdapter { - /** - * A list of columns containing the data to bind to the UI. - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected int[] mFrom; - /** - * A list of View ids representing the views to which the data must be bound. - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected int[] mTo; - - private int mStringConversionColumn = -1; - private CursorToStringConverter mCursorToStringConverter; - private ViewBinder mViewBinder; - - String[] mOriginalFrom; - - /** - * Constructor the enables auto-requery. - * - * @deprecated This option is discouraged, as it results in Cursor queries - * being performed on the application's UI thread and thus can cause poor - * responsiveness or even Application Not Responding errors. As an alternative, - * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}. - */ - @Deprecated - public SimpleDragSortCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) { - super(context, layout, c); - mTo = to; - mOriginalFrom = from; - findColumns(c, from); - } - - /** - * Standard constructor. - * - * @param context The context where the ListView associated with this - * SimpleListItemFactory is running - * @param layout resource identifier of a layout file that defines the views - * for this list item. The layout file should include at least - * those named views defined in "to" - * @param c The database cursor. Can be null if the cursor is not available yet. - * @param from A list of column names representing the data to bind to the UI. Can be null - * if the cursor is not available yet. - * @param to The views that should display column in the "from" parameter. - * These should all be TextViews. The first N views in this list - * are given the values of the first N columns in the from - * parameter. Can be null if the cursor is not available yet. - * @param flags Flags used to determine the behavior of the adapter, - * as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}. - */ - public SimpleDragSortCursorAdapter(Context context, int layout, - Cursor c, String[] from, int[] to, int flags) { - super(context, layout, c, flags); - mTo = to; - mOriginalFrom = from; - findColumns(c, from); - } - - /** - * Binds all of the field names passed into the "to" parameter of the - * constructor with their corresponding cursor columns as specified in the - * "from" parameter. - * - * Binding occurs in two phases. First, if a - * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available, - * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)} - * is invoked. If the returned value is true, binding has occured. If the - * returned value is false and the view to bind is a TextView, - * {@link #setViewText(TextView, String)} is invoked. If the returned value is - * false and the view to bind is an ImageView, - * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate - * binding can be found, an {@link IllegalStateException} is thrown. - * - * @throws IllegalStateException if binding cannot occur - * - * @see android.widget.CursorAdapter#bindView(android.view.View, - * android.content.Context, android.database.Cursor) - * @see #getViewBinder() - * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder) - * @see #setViewImage(ImageView, String) - * @see #setViewText(TextView, String) - */ - @Override - public void bindView(View view, Context context, Cursor cursor) { - final ViewBinder binder = mViewBinder; - final int count = mTo.length; - final int[] from = mFrom; - final int[] to = mTo; - - for (int i = 0; i < count; i++) { - final View v = view.findViewById(to[i]); - if (v != null) { - boolean bound = false; - if (binder != null) { - bound = binder.setViewValue(v, cursor, from[i]); - } - - if (!bound) { - String text = cursor.getString(from[i]); - if (text == null) { - text = ""; - } - - if (v instanceof TextView) { - setViewText((TextView) v, text); - } else if (v instanceof ImageView) { - setViewImage((ImageView) v, text); - } else { - throw new IllegalStateException(v.getClass().getName() + " is not a " + - " view that can be bounds by this SimpleCursorAdapter"); - } - } - } - } - } - - /** - * Returns the {@link ViewBinder} used to bind data to views. - * - * @return a ViewBinder or null if the binder does not exist - * - * @see #bindView(android.view.View, android.content.Context, android.database.Cursor) - * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder) - */ - public ViewBinder getViewBinder() { - return mViewBinder; - } - - /** - * Sets the binder used to bind data to views. - * - * @param viewBinder the binder used to bind data to views, can be null to - * remove the existing binder - * - * @see #bindView(android.view.View, android.content.Context, android.database.Cursor) - * @see #getViewBinder() - */ - public void setViewBinder(ViewBinder viewBinder) { - mViewBinder = viewBinder; - } - - /** - * Called by bindView() to set the image for an ImageView but only if - * there is no existing ViewBinder or if the existing ViewBinder cannot - * handle binding to an ImageView. - * - * By default, the value will be treated as an image resource. If the - * value cannot be used as an image resource, the value is used as an - * image Uri. - * - * Intended to be overridden by Adapters that need to filter strings - * retrieved from the database. - * - * @param v ImageView to receive an image - * @param value the value retrieved from the cursor - */ - public void setViewImage(ImageView v, String value) { - try { - v.setImageResource(Integer.parseInt(value)); - } catch (NumberFormatException nfe) { - v.setImageURI(Uri.parse(value)); - } - } - - /** - * Called by bindView() to set the text for a TextView but only if - * there is no existing ViewBinder or if the existing ViewBinder cannot - * handle binding to a TextView. - * - * Intended to be overridden by Adapters that need to filter strings - * retrieved from the database. - * - * @param v TextView to receive text - * @param text the text to be set for the TextView - */ - public void setViewText(TextView v, String text) { - v.setText(text); - } - - /** - * Return the index of the column used to get a String representation - * of the Cursor. - * - * @return a valid index in the current Cursor or -1 - * - * @see android.widget.CursorAdapter#convertToString(android.database.Cursor) - * @see #setStringConversionColumn(int) - * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) - * @see #getCursorToStringConverter() - */ - public int getStringConversionColumn() { - return mStringConversionColumn; - } - - /** - * Defines the index of the column in the Cursor used to get a String - * representation of that Cursor. The column is used to convert the - * Cursor to a String only when the current CursorToStringConverter - * is null. - * - * @param stringConversionColumn a valid index in the current Cursor or -1 to use the default - * conversion mechanism - * - * @see android.widget.CursorAdapter#convertToString(android.database.Cursor) - * @see #getStringConversionColumn() - * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) - * @see #getCursorToStringConverter() - */ - public void setStringConversionColumn(int stringConversionColumn) { - mStringConversionColumn = stringConversionColumn; - } - - /** - * Returns the converter used to convert the filtering Cursor - * into a String. - * - * @return null if the converter does not exist or an instance of - * {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} - * - * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) - * @see #getStringConversionColumn() - * @see #setStringConversionColumn(int) - * @see android.widget.CursorAdapter#convertToString(android.database.Cursor) - */ - public CursorToStringConverter getCursorToStringConverter() { - return mCursorToStringConverter; - } - - /** - * Sets the converter used to convert the filtering Cursor - * into a String. - * - * @param cursorToStringConverter the Cursor to String converter, or - * null to remove the converter - * - * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter) - * @see #getStringConversionColumn() - * @see #setStringConversionColumn(int) - * @see android.widget.CursorAdapter#convertToString(android.database.Cursor) - */ - public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) { - mCursorToStringConverter = cursorToStringConverter; - } - - /** - * Returns a CharSequence representation of the specified Cursor as defined - * by the current CursorToStringConverter. If no CursorToStringConverter - * has been set, the String conversion column is used instead. If the - * conversion column is -1, the returned String is empty if the cursor - * is null or Cursor.toString(). - * - * @param cursor the Cursor to convert to a CharSequence - * - * @return a non-null CharSequence representing the cursor - */ - @Override - public CharSequence convertToString(Cursor cursor) { - if (mCursorToStringConverter != null) { - return mCursorToStringConverter.convertToString(cursor); - } else if (mStringConversionColumn > -1) { - return cursor.getString(mStringConversionColumn); - } - - return super.convertToString(cursor); - } - - /** - * Create a map from an array of strings to an array of column-id integers in cursor c. - * If c is null, the array will be discarded. - * - * @param c the cursor to find the columns from - * @param from the Strings naming the columns of interest - */ - private void findColumns(Cursor c, String[] from) { - if (c != null) { - int i; - int count = from.length; - if (mFrom == null || mFrom.length != count) { - mFrom = new int[count]; - } - for (i = 0; i < count; i++) { - mFrom[i] = c.getColumnIndexOrThrow(from[i]); - } - } else { - mFrom = null; - } - } - - @Override - public Cursor swapCursor(Cursor c) { - // super.swapCursor() will notify observers before we have - // a valid mapping, make sure we have a mapping before this - // happens - findColumns(c, mOriginalFrom); - return super.swapCursor(c); - } - - /** - * Change the cursor and change the column-to-view mappings at the same time. - * - * @param c The database cursor. Can be null if the cursor is not available yet. - * @param from A list of column names representing the data to bind to the UI. Can be null - * if the cursor is not available yet. - * @param to The views that should display column in the "from" parameter. - * These should all be TextViews. The first N views in this list - * are given the values of the first N columns in the from - * parameter. Can be null if the cursor is not available yet. - */ - public void changeCursorAndColumns(Cursor c, String[] from, int[] to) { - mOriginalFrom = from; - mTo = to; - // super.changeCursor() will notify observers before we have - // a valid mapping, make sure we have a mapping before this - // happens - findColumns(c, mOriginalFrom); - super.changeCursor(c); - } - - /** - * This class can be used by external clients of SimpleCursorAdapter - * to bind values fom the Cursor to views. - * - * You should use this class to bind values from the Cursor to views - * that are not directly supported by SimpleCursorAdapter or to - * change the way binding occurs for views supported by - * SimpleCursorAdapter. - * - * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor) - * @see SimpleCursorAdapter#setViewImage(ImageView, String) - * @see SimpleCursorAdapter#setViewText(TextView, String) - */ - public static interface ViewBinder { - /** - * Binds the Cursor column defined by the specified index to the specified view. - * - * When binding is handled by this ViewBinder, this method must return true. - * If this method returns false, SimpleCursorAdapter will attempts to handle - * the binding on its own. - * - * @param view the view to bind the data to - * @param cursor the cursor to get the data from - * @param columnIndex the column at which the data can be found in the cursor - * - * @return true if the data was bound to the view, false otherwise - */ - boolean setViewValue(View view, Cursor cursor, int columnIndex); - } - - /** - * This class can be used by external clients of SimpleCursorAdapter - * to define how the Cursor should be converted to a String. - * - * @see android.widget.CursorAdapter#convertToString(android.database.Cursor) - */ - public static interface CursorToStringConverter { - /** - * Returns a CharSequence representing the specified Cursor. - * - * @param cursor the cursor for which a CharSequence representation - * is requested - * - * @return a non-null CharSequence representing the cursor - */ - CharSequence convertToString(Cursor cursor); - } - -} diff --git a/core/library/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java b/core/library/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java deleted file mode 100644 index 43505a4d..00000000 --- a/core/library/src/main/java/com/mobeta/android/dslv/SimpleFloatViewManager.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.mobeta.android.dslv; - -import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.Color; -import android.widget.ListView; -import android.widget.ImageView; -import android.view.View; -import android.view.ViewGroup; -import timber.log.Timber; - -/** - * Simple implementation of the FloatViewManager class. Uses list - * items as they appear in the ListView to create the floating View. - */ -public class SimpleFloatViewManager implements DragSortListView.FloatViewManager { - - private Bitmap mFloatBitmap; - - private ImageView mImageView; - - private int mFloatBGColor = Color.BLACK; - - private ListView mListView; - - public SimpleFloatViewManager(ListView lv) { - mListView = lv; - } - - public void setBackgroundColor(int color) { - mFloatBGColor = color; - } - - /** - * This simple implementation creates a Bitmap copy of the - * list item currently shown at ListView position. - */ - @Override - public View onCreateFloatView(int position) { - // Guaranteed that this will not be null? I think so. Nope, got - // a NullPointerException once... - View v = mListView.getChildAt(position + mListView.getHeaderViewsCount() - mListView.getFirstVisiblePosition()); - - if (v == null) { - return null; - } - - v.setPressed(false); - - // Create a copy of the drawing cache so that it does not get - // recycled by the framework when the list tries to clean up memory - //v.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); - v.setDrawingCacheEnabled(true); - mFloatBitmap = Bitmap.createBitmap(v.getDrawingCache()); - v.setDrawingCacheEnabled(false); - - if (mImageView == null) { - mImageView = new ImageView(mListView.getContext()); - } - mImageView.setBackgroundColor(mFloatBGColor); - mImageView.setPadding(0, 0, 0, 0); - mImageView.setImageBitmap(mFloatBitmap); - mImageView.setLayoutParams(new ViewGroup.LayoutParams(v.getWidth(), v.getHeight())); - - return mImageView; - } - - /** - * This does nothing - */ - @Override - public void onDragFloatView(View floatView, Point position, Point touch) { - // do nothing - } - - /** - * Removes the Bitmap from the ImageView created in - * onCreateFloatView() and tells the system to recycle it. - */ - @Override - public void onDestroyFloatView(View floatView) { - ((ImageView) floatView).setImageDrawable(null); - - mFloatBitmap.recycle(); - mFloatBitmap = null; - } - -} - diff --git a/core/library/src/main/res/values/dslv_attrs.xml b/core/library/src/main/res/values/dslv_attrs.xml deleted file mode 100644 index 6c4d5d79..00000000 --- a/core/library/src/main/res/values/dslv_attrs.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dependencies.gradle b/dependencies.gradle index c139954f..9dc6ce74 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -31,6 +31,7 @@ ext.versions = [ twitterSerial : "0.1.6", koin : "2.2.2", picasso : "2.71828", + sortListView : "1.0", junit4 : "4.13.2", junit5 : "5.7.1", @@ -89,6 +90,7 @@ ext.other = [ dexter : "com.karumi:dexter:$versions.dexter", timber : "com.jakewharton.timber:timber:$versions.timber", fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll", + sortListView : "asia.ivity.android:drag-sort-listview:$versions.sortListView", ] ext.testing = [ diff --git a/settings.gradle b/settings.gradle index dc51ee7a..140eafe6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,3 @@ -include ':core:library' include ':core:domain' include ':core:subsonic-api' include ':core:subsonic-api-image-loader' diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 0adc0b0b..53a2929e 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -66,7 +66,6 @@ tasks.withType(Test) { } dependencies { - implementation project(':core:library') implementation project(':core:domain') implementation project(':core:subsonic-api') implementation project(':core:subsonic-api-image-loader') @@ -95,6 +94,7 @@ dependencies { implementation other.koinViewModel implementation other.okhttpLogging implementation other.fastScroll + implementation other.sortListView kapt androidSupport.room diff --git a/ultrasonic/src/main/res/layout/current_playlist.xml b/ultrasonic/src/main/res/layout/current_playlist.xml index 1b933376..63d1f73e 100644 --- a/ultrasonic/src/main/res/layout/current_playlist.xml +++ b/ultrasonic/src/main/res/layout/current_playlist.xml @@ -27,7 +27,6 @@ app:remove_mode="flingRemove" app:fling_handle_id="@+id/song_drag" app:drag_start_mode="onMove" - app:float_background_color="?attr/color_background" - app:float_alpha="0.7" /> + app:float_background_color="?attr/color_background" /> \ No newline at end of file From a25a2ff3373ecb2486015b5834b0474c2f14e5ed Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 12:39:20 +0200 Subject: [PATCH 02/14] Go back to default detekt checks. Type resolution (which I had enabled some weeks ago) is really not stable enough for production and also takes much longer time to compile. --- .circleci/config.yml | 2 +- detekt-baseline-main.xml | 48 ------ detekt-baseline-release.xml | 158 ------------------ ...-baseline-debug.xml => detekt-baseline.xml | 50 +++++- 4 files changed, 44 insertions(+), 214 deletions(-) delete mode 100644 detekt-baseline-main.xml delete mode 100644 detekt-baseline-release.xml rename detekt-baseline-debug.xml => detekt-baseline.xml (81%) diff --git a/.circleci/config.yml b/.circleci/config.yml index e2b2238e..36bef6a5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: command: ./gradlew -Pqc ktlintCheck - run: name: static analysis - command: ./gradlew -Pqc detektMain + command: ./gradlew -Pqc detekt - run: name: build command: ./gradlew assembleDebug diff --git a/detekt-baseline-main.xml b/detekt-baseline-main.xml deleted file mode 100644 index b43d9b0e..00000000 --- a/detekt-baseline-main.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - ComplexMethod:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions - EmptyFunctionBlock:SubsonicAPIClient.kt$SubsonicAPIClient.<no name provided>${} - MagicNumber:PasswordExt.kt$0xFF - MagicNumber:PasswordExt.kt$4 - MagicNumber:PasswordMD5Interceptor.kt$PasswordMD5Interceptor$16 - MagicNumber:StreamResponse.kt$StreamResponse$200 - MagicNumber:StreamResponse.kt$StreamResponse$300 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$10 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$11 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$12 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$13 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$14 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$15 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$16 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$3 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$4 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$5 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$6 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$7 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$8 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$9 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$10 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$20 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$30 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$40 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$41 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$50 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$60 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$70 - MagicNumber:SubsonicError.kt$SubsonicError.IncompatibleClientProtocolVersion$20 - MagicNumber:SubsonicError.kt$SubsonicError.IncompatibleServerProtocolVersion$30 - MagicNumber:SubsonicError.kt$SubsonicError.RequestedDataWasNotFound$70 - MagicNumber:SubsonicError.kt$SubsonicError.RequiredParamMissing$10 - MagicNumber:SubsonicError.kt$SubsonicError.TokenAuthNotSupportedForLDAP$41 - MagicNumber:SubsonicError.kt$SubsonicError.TrialPeriodIsOver$60 - MagicNumber:SubsonicError.kt$SubsonicError.UserNotAuthorizedForOperation$50 - MagicNumber:SubsonicError.kt$SubsonicError.WrongUsernameOrPassword$40 - ReturnCount:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions - SwallowedException:VersionAwareJacksonConverterFactory.kt$VersionAwareJacksonConverterFactory.VersionAwareResponseBodyConverter$catch (e: IllegalArgumentException) { // no-op } - ThrowsCount:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions - TooManyFunctions:ApiVersionCheckWrapper.kt$ApiVersionCheckWrapper : SubsonicAPIDefinition - UnusedPrivateMember:AlbumListType.kt$AlbumListType.Companion$private operator fun String.contains(other: String) - - diff --git a/detekt-baseline-release.xml b/detekt-baseline-release.xml deleted file mode 100644 index 7edc104a..00000000 --- a/detekt-baseline-release.xml +++ /dev/null @@ -1,158 +0,0 @@ - - - - - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun areFieldsChanged(): Boolean - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun finishActivity() - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun getFields(): Boolean - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun setFields() - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun testConnection() - CommentOverPrivateFunction:FileLoggerTree.kt$FileLoggerTree$ private fun getNextLogFile() - CommentOverPrivateFunction:FileLoggerTree.kt$FileLoggerTree$ private fun getNumberedFile(next: Boolean) - CommentOverPrivateFunction:MediaPlayerService.kt$MediaPlayerService$ private fun buildForegroundNotification( playerState: PlayerState, currentPlaying: DownloadFile? ): Notification - CommentOverPrivateFunction:RESTMusicService.kt$RESTMusicService$ @Throws(Exception::class) private fun search2( criteria: SearchCriteria ): SearchResult - CommentOverPrivateFunction:RESTMusicService.kt$RESTMusicService$ @Throws(Exception::class) private fun searchOld( criteria: SearchCriteria ): SearchResult - CommentOverPrivateFunction:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean - CommentOverPrivateFunction:ServerRowAdapter.kt$ServerRowAdapter$ private fun serverMenuClick(view: View, position: Int) - CommentOverPrivateFunction:ServerSelectorFragment.kt$ServerSelectorFragment$ private fun editServer(index: Int) - CommentOverPrivateFunction:ServerSelectorFragment.kt$ServerSelectorFragment$ private fun onServerDeleted(index: Int) - CommentOverPrivateFunction:ServerSelectorFragment.kt$ServerSelectorFragment$ private fun setActiveServer(index: Int) - CommentOverPrivateFunction:ServerSettingsModel.kt$ServerSettingsModel$ private fun loadServerSettingFromPreferences( preferenceId: Int, serverId: Int, settings: SharedPreferences ): ServerSetting? - CommentOverPrivateFunction:ServerSettingsModel.kt$ServerSettingsModel$ private suspend fun areIndexesMissing(): Boolean - CommentOverPrivateFunction:ServerSettingsModel.kt$ServerSettingsModel$ private suspend fun reindexSettings() - ComplexCondition:DownloadHandler.kt$DownloadHandler.<no name provided>$!append && !playNext && !unpin && !background - ComplexCondition:FilePickerAdapter.kt$FilePickerAdapter$currentDirectory.absolutePath == "/" || currentDirectory.absolutePath == "/storage" || currentDirectory.absolutePath == "/storage/emulated" || currentDirectory.absolutePath == "/mnt" - ComplexCondition:LocalMediaPlayer.kt$LocalMediaPlayer$Util.getGaplessPlaybackPreference() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && ( playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED ) - ComplexCondition:SongView.kt$SongView$TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo && Util.getVideoPlayerType() !== VideoPlayerType.FLASH - ComplexMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() - ComplexMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File) - ComplexMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean) - ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun enableButtons() - ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) - EmptyCatchBlock:LocalMediaPlayer.kt$LocalMediaPlayer${ } - EmptyDefaultConstructor:VideoPlayer.kt$VideoPlayer$() - EmptyFunctionBlock:SongView.kt$SongView${} - FunctionNaming:ThemeChangedEventDistributor.kt$ThemeChangedEventDistributor$fun RaiseThemeChangedEvent() - ImplicitDefaultLocale:DownloadFile.kt$DownloadFile$String.format("DownloadFile (%s)", song) - ImplicitDefaultLocale:DownloadFile.kt$DownloadFile.DownloadTask$String.format("Download of '%s' was cancelled", song) - ImplicitDefaultLocale:DownloadFile.kt$DownloadFile.DownloadTask$String.format("DownloadTask (%s)", song) - ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.<no name provided>$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) ) - ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Failed to write log to %s", file) - ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Log file rotated, logging into file %s", file?.name) - ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Logging into file %s", file?.name) - ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$String.format("BufferTask (%s)", downloadFile) - ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$String.format("CheckCompletionTask (%s)", downloadFile) - ImplicitDefaultLocale:ShareHandler.kt$ShareHandler$String.format("%d:%s", timeSpanAmount, timeSpanType) - ImplicitDefaultLocale:ShareHandler.kt$ShareHandler.<no name provided>$String.format("%s\n\n%s", Util.getShareGreeting(context), result.url) - ImplicitDefaultLocale:SongView.kt$SongView$String.format("%02d.", trackNumber) - ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s ", bitRate) - ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s > %s", suffix, transcodedSuffix) - LargeClass:MediaPlayerService.kt$MediaPlayerService : Service - LargeClass:RESTMusicService.kt$RESTMusicService : MusicService - LargeClass:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment - LongMethod:ArtistListFragment.kt$ArtistListFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) - LongMethod:ArtistListFragment.kt$ArtistListFragment$private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean - LongMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() - LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) - LongMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File) - LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun doPlay(downloadFile: DownloadFile, position: Int, start: Boolean) - LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun updateMediaSession(currentPlaying: DownloadFile?, playerState: PlayerState) - LongMethod:NavigationActivity.kt$NavigationActivity$override fun onCreate(savedInstanceState: Bundle?) - LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken ) - LongMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean) - LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean - LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) - LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateDisplay(refresh: Boolean) - LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) - LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, append: Boolean, save: Boolean, autoPlay: Boolean, playNext: Boolean, shuffle: Boolean, songs: List<MusicDirectory.Entry?> ) - LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) - LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, id: String, name: String?, save: Boolean, append: Boolean, autoplay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean ) - LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, id: String?, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) - LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, private var data: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) ) - MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192 - MagicNumber:AudioFocusHandler.kt$AudioFocusHandler$0.1f - MagicNumber:DownloadFile.kt$DownloadFile$100 - MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$10 - MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$1000L - MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$60 - MagicNumber:DownloadHandler.kt$DownloadHandler$500 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$100 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$3 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$4 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$5 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$6 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$7 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer$100 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer$1000 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$1000 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$60000 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$100000 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$1000L - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$1024L - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$86400L - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8L - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$5000L - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$50L - MagicNumber:MediaPlayerService.kt$MediaPlayerService$100 - MagicNumber:MediaPlayerService.kt$MediaPlayerService$1000 - MagicNumber:MediaPlayerService.kt$MediaPlayerService$256 - MagicNumber:MediaPlayerService.kt$MediaPlayerService$3 - MagicNumber:MediaPlayerService.kt$MediaPlayerService$4 - MagicNumber:MediaPlayerService.kt$MediaPlayerService.Companion$19 - MagicNumber:MediaPlayerService.kt$MediaPlayerService.Companion$50L - MagicNumber:RESTMusicService.kt$RESTMusicService$206 - MagicNumber:RESTMusicService.kt$RESTMusicService$5 - MagicNumber:SelectMusicFolderView.kt$SelectMusicFolderView$10 - MagicNumber:SongView.kt$SongView$3 - MagicNumber:SongView.kt$SongView$4 - MagicNumber:SongView.kt$SongView$60 - MagicNumber:TrackCollectionFragment.kt$TrackCollectionFragment$10 - NestedBlockDepth:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() - NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) - NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler() - ReturnCount:ActiveServerProvider.kt$ActiveServerProvider$ fun getActiveServer(): ServerSetting - ReturnCount:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String - ReturnCount:FileLoggerTree.kt$FileLoggerTree$ private fun getNextLogFile() - ReturnCount:MediaPlayerService.kt$MediaPlayerService$private fun generateAction(context: Context, requestCode: Int): NotificationCompat.Action? - ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getAvatar( username: String?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap? - ReturnCount:RESTMusicService.kt$RESTMusicService$@Throws(Exception::class) override fun getCoverArt( entry: MusicDirectory.Entry?, size: Int, saveToFile: Boolean, highQuality: Boolean ): Bitmap? - ReturnCount:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean - ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean - ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onOptionsItemSelected(item: MenuItem): Boolean - SpreadOperator:MediaPlayerService.kt$MediaPlayerService$(*compactActions) - SwallowedException:DownloadFile.kt$DownloadFile$catch (e: Exception) { Timber.w("Failed to set last-modified date on %s", file) } - SwallowedException:DownloadFile.kt$DownloadFile$catch (ex: IOException) { Timber.w("Failed to rename file %s to %s", completeFile, saveFile) } - SwallowedException:LocalMediaPlayer.kt$LocalMediaPlayer$catch (e: Throwable) { // Froyo or lower } - SwallowedException:LocalMediaPlayer.kt$LocalMediaPlayer$catch (e: Throwable) { } - SwallowedException:MediaPlayerService.kt$MediaPlayerService$catch (x: IndexOutOfBoundsException) { // Ignored } - SwallowedException:NavigationActivity.kt$NavigationActivity$catch (e: Resources.NotFoundException) { destination.id.toString() } - ThrowsCount:ApiCallResponseChecker.kt$ApiCallResponseChecker.Companion$@Throws(SubsonicRESTException::class, IOException::class) fun checkResponseSuccessful(response: Response<out SubsonicResponse>) - TooGenericExceptionCaught:ArtistListModel.kt$ArtistListModel$exception: Exception - TooGenericExceptionCaught:DownloadFile.kt$DownloadFile$e: Exception - TooGenericExceptionCaught:DownloadFile.kt$DownloadFile.DownloadTask$x: Exception - TooGenericExceptionCaught:FileLoggerTree.kt$FileLoggerTree$x: Throwable - TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$e: Throwable - TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$ex: Exception - TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$exception: Throwable - TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$x: Exception - TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$e: Exception - TooGenericExceptionCaught:MediaPlayerService.kt$MediaPlayerService$e: Exception - TooGenericExceptionCaught:MediaPlayerService.kt$MediaPlayerService$x: IndexOutOfBoundsException - TooGenericExceptionCaught:SongView.kt$SongView$e: Exception - TooGenericExceptionCaught:SubsonicUncaughtExceptionHandler.kt$SubsonicUncaughtExceptionHandler$x: Throwable - TooGenericExceptionCaught:VideoPlayer.kt$VideoPlayer$e: Exception - TooGenericExceptionThrown:DownloadFile.kt$DownloadFile.DownloadTask$throw Exception(String.format("Download of '%s' was cancelled", song)) - TooManyFunctions:LocalMediaPlayer.kt$LocalMediaPlayer - TooManyFunctions:MediaPlayerService.kt$MediaPlayerService : Service - TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService - TooManyFunctions:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment - TopLevelPropertyNaming:SubsonicUncaughtExceptionHandler.kt$private const val filename = "ultrasonic-stacktrace.txt" - UnusedPrivateMember:RESTMusicService.kt$RESTMusicService.Companion$private const val INDEXES_FOLDER_STORAGE_NAME = "indexes_folder" - UselessCallOnNotNull:FileLoggerTree.kt$FileLoggerTree$fileList.isNullOrEmpty() - UselessCallOnNotNull:FileLoggerTree.kt$FileLoggerTree.Companion$fileList.isNullOrEmpty() - UtilityClassWithPublicConstructor:CommunicationErrorHandler.kt$CommunicationErrorHandler - UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle - VariableNaming:SelectMusicFolderView.kt$SelectMusicFolderView$private val MENU_GROUP_MUSIC_FOLDER = 10 - - diff --git a/detekt-baseline-debug.xml b/detekt-baseline.xml similarity index 81% rename from detekt-baseline-debug.xml rename to detekt-baseline.xml index 7edc104a..61ee636a 100644 --- a/detekt-baseline-debug.xml +++ b/detekt-baseline.xml @@ -2,6 +2,48 @@ + ComplexMethod:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions + EmptyFunctionBlock:SubsonicAPIClient.kt$SubsonicAPIClient.<no name provided>${} + MagicNumber:PasswordExt.kt$0xFF + MagicNumber:PasswordExt.kt$4 + MagicNumber:PasswordMD5Interceptor.kt$PasswordMD5Interceptor$16 + MagicNumber:StreamResponse.kt$StreamResponse$200 + MagicNumber:StreamResponse.kt$StreamResponse$300 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$10 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$11 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$12 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$13 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$14 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$15 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$16 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$3 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$4 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$5 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$6 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$7 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$8 + MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$9 + MagicNumber:SubsonicError.kt$SubsonicError.Companion$10 + MagicNumber:SubsonicError.kt$SubsonicError.Companion$20 + MagicNumber:SubsonicError.kt$SubsonicError.Companion$30 + MagicNumber:SubsonicError.kt$SubsonicError.Companion$40 + MagicNumber:SubsonicError.kt$SubsonicError.Companion$41 + MagicNumber:SubsonicError.kt$SubsonicError.Companion$50 + MagicNumber:SubsonicError.kt$SubsonicError.Companion$60 + MagicNumber:SubsonicError.kt$SubsonicError.Companion$70 + MagicNumber:SubsonicError.kt$SubsonicError.IncompatibleClientProtocolVersion$20 + MagicNumber:SubsonicError.kt$SubsonicError.IncompatibleServerProtocolVersion$30 + MagicNumber:SubsonicError.kt$SubsonicError.RequestedDataWasNotFound$70 + MagicNumber:SubsonicError.kt$SubsonicError.RequiredParamMissing$10 + MagicNumber:SubsonicError.kt$SubsonicError.TokenAuthNotSupportedForLDAP$41 + MagicNumber:SubsonicError.kt$SubsonicError.TrialPeriodIsOver$60 + MagicNumber:SubsonicError.kt$SubsonicError.UserNotAuthorizedForOperation$50 + MagicNumber:SubsonicError.kt$SubsonicError.WrongUsernameOrPassword$40 + ReturnCount:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions + SwallowedException:VersionAwareJacksonConverterFactory.kt$VersionAwareJacksonConverterFactory.VersionAwareResponseBodyConverter$catch (e: IllegalArgumentException) { // no-op } + ThrowsCount:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions + TooManyFunctions:ApiVersionCheckWrapper.kt$ApiVersionCheckWrapper : SubsonicAPIDefinition + UnusedPrivateMember:AlbumListType.kt$AlbumListType.Companion$private operator fun String.contains(other: String) CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun areFieldsChanged(): Boolean CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun finishActivity() CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun getFields(): Boolean @@ -50,8 +92,6 @@ LargeClass:MediaPlayerService.kt$MediaPlayerService : Service LargeClass:RESTMusicService.kt$RESTMusicService : MusicService LargeClass:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment - LongMethod:ArtistListFragment.kt$ArtistListFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) - LongMethod:ArtistListFragment.kt$ArtistListFragment$private fun onArtistMenuItemSelected(menuItem: MenuItem, artist: Artist): Boolean LongMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) LongMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File) @@ -103,7 +143,6 @@ MagicNumber:MediaPlayerService.kt$MediaPlayerService.Companion$50L MagicNumber:RESTMusicService.kt$RESTMusicService$206 MagicNumber:RESTMusicService.kt$RESTMusicService$5 - MagicNumber:SelectMusicFolderView.kt$SelectMusicFolderView$10 MagicNumber:SongView.kt$SongView$3 MagicNumber:SongView.kt$SongView$4 MagicNumber:SongView.kt$SongView$60 @@ -128,7 +167,6 @@ SwallowedException:MediaPlayerService.kt$MediaPlayerService$catch (x: IndexOutOfBoundsException) { // Ignored } SwallowedException:NavigationActivity.kt$NavigationActivity$catch (e: Resources.NotFoundException) { destination.id.toString() } ThrowsCount:ApiCallResponseChecker.kt$ApiCallResponseChecker.Companion$@Throws(SubsonicRESTException::class, IOException::class) fun checkResponseSuccessful(response: Response<out SubsonicResponse>) - TooGenericExceptionCaught:ArtistListModel.kt$ArtistListModel$exception: Exception TooGenericExceptionCaught:DownloadFile.kt$DownloadFile$e: Exception TooGenericExceptionCaught:DownloadFile.kt$DownloadFile.DownloadTask$x: Exception TooGenericExceptionCaught:FileLoggerTree.kt$FileLoggerTree$x: Throwable @@ -149,10 +187,8 @@ TooManyFunctions:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment TopLevelPropertyNaming:SubsonicUncaughtExceptionHandler.kt$private const val filename = "ultrasonic-stacktrace.txt" UnusedPrivateMember:RESTMusicService.kt$RESTMusicService.Companion$private const val INDEXES_FOLDER_STORAGE_NAME = "indexes_folder" - UselessCallOnNotNull:FileLoggerTree.kt$FileLoggerTree$fileList.isNullOrEmpty() - UselessCallOnNotNull:FileLoggerTree.kt$FileLoggerTree.Companion$fileList.isNullOrEmpty() UtilityClassWithPublicConstructor:CommunicationErrorHandler.kt$CommunicationErrorHandler UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle - VariableNaming:SelectMusicFolderView.kt$SelectMusicFolderView$private val MENU_GROUP_MUSIC_FOLDER = 10 + UnnecessaryAbstractClass:BaseStorageTest.kt$BaseStorageTest From 689b704bc5f3e7105f33a16e4b6e1cc91e2a6a22 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 18:08:16 +0200 Subject: [PATCH 03/14] Enable parallel AST generation --- gradle_scripts/code_quality.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gradle_scripts/code_quality.gradle b/gradle_scripts/code_quality.gradle index b4c49e1b..e6a23903 100644 --- a/gradle_scripts/code_quality.gradle +++ b/gradle_scripts/code_quality.gradle @@ -22,6 +22,9 @@ if (isCodeQualityEnabled) { detekt { buildUponDefaultConfig = true toolVersion = versions.detekt + // Builds the AST in parallel. Rules are always executed in parallel. + // Can lead to speedups in larger projects. + parallel = true baseline = file("${rootProject.projectDir}/detekt-baseline.xml") config = files("${rootProject.projectDir}/detekt-config.yml") } From b559f6420e366e278b1f4fa786c0145b115e48c3 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 19:24:30 +0200 Subject: [PATCH 04/14] Move certain ignores from the baseline to the specified files. This makes refactoring much less fragile. --- .../api/subsonic/ApiVersionCheckWrapper.kt | 1 + .../api/subsonic/SubsonicAPIClient.kt | 2 +- .../api/subsonic/SubsonicAPIVersions.kt | 1 + .../ultrasonic/api/subsonic/SubsonicError.kt | 1 + .../VersionAwareJacksonConverterFactory.kt | 1 + .../api/subsonic/interceptors/PasswordExt.kt | 1 + .../api/subsonic/models/AlbumListType.kt | 1 + .../api/subsonic/response/StreamResponse.kt | 1 + detekt-baseline.xml | 84 ------------------- detekt-config.yml | 10 ++- .../moire/ultrasonic/log/FileLoggerTree.kt | 1 + .../moire/ultrasonic/service/DownloadFile.kt | 18 ++-- .../ultrasonic/service/MediaPlayerService.kt | 3 + .../ultrasonic/subsonic/DownloadHandler.kt | 1 + .../util/SubsonicUncaughtExceptionHandler.kt | 8 +- 15 files changed, 35 insertions(+), 99 deletions(-) diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt index 3377acfc..20923b0b 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/ApiVersionCheckWrapper.kt @@ -42,6 +42,7 @@ import retrofit2.Call * Special wrapper for [SubsonicAPIDefinition] that checks if [currentApiVersion] is suitable * for this call. */ +@Suppress("TooManyFunctions") internal class ApiVersionCheckWrapper( val api: SubsonicAPIDefinition, var currentApiVersion: SubsonicAPIVersions diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt index 4455a3b8..fb6ce161 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIClient.kt @@ -183,7 +183,7 @@ class SubsonicAPIClient( this.addInterceptor(loggingInterceptor) } - @SuppressWarnings("TrustAllX509TrustManager") + @SuppressWarnings("TrustAllX509TrustManager", "EmptyFunctionBlock") private fun OkHttpClient.Builder.allowSelfSignedCertificates() { val trustManager = object : X509TrustManager { override fun checkClientTrusted(p0: Array?, p1: String?) {} diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersions.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersions.kt index fbdc27c4..d2717da2 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersions.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicAPIVersions.kt @@ -10,6 +10,7 @@ import java.lang.NumberFormatException /** * Subsonic REST API versions. */ +@Suppress("MagicNumber", "ComplexMethod", "ReturnCount", "ThrowsCount") @JsonDeserialize(using = SubsonicAPIVersions.Companion.SubsonicAPIVersionsDeserializer::class) enum class SubsonicAPIVersions(val subsonicVersions: String, val restApiVersion: String) { V1_1_0("3.8", "1.1.0"), diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt index ad1419e4..0acc6486 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/SubsonicError.kt @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize /** * Common API errors. */ +@Suppress("MagicNumber") @JsonDeserialize(using = SubsonicError.Companion.SubsonicErrorDeserializer::class) sealed class SubsonicError(val code: Int) { data class Generic(val message: String) : SubsonicError(0) diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/VersionAwareJacksonConverterFactory.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/VersionAwareJacksonConverterFactory.kt index 347e6864..66ba8d8b 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/VersionAwareJacksonConverterFactory.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/VersionAwareJacksonConverterFactory.kt @@ -63,6 +63,7 @@ class VersionAwareJacksonConverterFactory( } } + @Suppress("SwallowedException") class VersionAwareResponseBodyConverter ( private val notifier: (SubsonicAPIVersions) -> Unit = {}, private val adapter: ObjectReader diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/PasswordExt.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/PasswordExt.kt index 307fc49e..1b449ed1 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/PasswordExt.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/interceptors/PasswordExt.kt @@ -11,6 +11,7 @@ fun String.toHexBytes(): String = this.toByteArray().toHexBytes() /** * Converts given [ByteArray] to corresponding hex chars representation. */ +@Suppress("MagicNumber") fun ByteArray.toHexBytes(): String { val hexChars = CharArray(this.size * 2) for (j in 0..this.lastIndex) { diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/AlbumListType.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/AlbumListType.kt index 8ab52824..6d58eb0d 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/AlbumListType.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/models/AlbumListType.kt @@ -38,6 +38,7 @@ enum class AlbumListType(val typeName: String) { else -> throw IllegalArgumentException("Unknown type: $typeName") } + @Suppress("UnusedPrivateMember") // Used in the tests private operator fun String.contains(other: String) = this.equals(other, true) } } diff --git a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/StreamResponse.kt b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/StreamResponse.kt index 8b4cb060..f1d15aa2 100644 --- a/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/StreamResponse.kt +++ b/core/subsonic-api/src/main/kotlin/org/moire/ultrasonic/api/subsonic/response/StreamResponse.kt @@ -9,6 +9,7 @@ import org.moire.ultrasonic.api.subsonic.SubsonicError * * [responseHttpCode] will be there always. */ +@Suppress("MagicNumber") class StreamResponse( val stream: InputStream? = null, val apiError: SubsonicError? = null, diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 61ee636a..8c0868eb 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -2,66 +2,6 @@ - ComplexMethod:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions - EmptyFunctionBlock:SubsonicAPIClient.kt$SubsonicAPIClient.<no name provided>${} - MagicNumber:PasswordExt.kt$0xFF - MagicNumber:PasswordExt.kt$4 - MagicNumber:PasswordMD5Interceptor.kt$PasswordMD5Interceptor$16 - MagicNumber:StreamResponse.kt$StreamResponse$200 - MagicNumber:StreamResponse.kt$StreamResponse$300 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$10 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$11 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$12 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$13 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$14 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$15 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$16 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$3 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$4 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$5 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$6 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$7 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$8 - MagicNumber:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$9 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$10 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$20 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$30 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$40 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$41 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$50 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$60 - MagicNumber:SubsonicError.kt$SubsonicError.Companion$70 - MagicNumber:SubsonicError.kt$SubsonicError.IncompatibleClientProtocolVersion$20 - MagicNumber:SubsonicError.kt$SubsonicError.IncompatibleServerProtocolVersion$30 - MagicNumber:SubsonicError.kt$SubsonicError.RequestedDataWasNotFound$70 - MagicNumber:SubsonicError.kt$SubsonicError.RequiredParamMissing$10 - MagicNumber:SubsonicError.kt$SubsonicError.TokenAuthNotSupportedForLDAP$41 - MagicNumber:SubsonicError.kt$SubsonicError.TrialPeriodIsOver$60 - MagicNumber:SubsonicError.kt$SubsonicError.UserNotAuthorizedForOperation$50 - MagicNumber:SubsonicError.kt$SubsonicError.WrongUsernameOrPassword$40 - ReturnCount:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions - SwallowedException:VersionAwareJacksonConverterFactory.kt$VersionAwareJacksonConverterFactory.VersionAwareResponseBodyConverter$catch (e: IllegalArgumentException) { // no-op } - ThrowsCount:SubsonicAPIVersions.kt$SubsonicAPIVersions.Companion$@JvmStatic @Throws(IllegalArgumentException::class) fun getClosestKnownClientApiVersion(apiVersion: String): SubsonicAPIVersions - TooManyFunctions:ApiVersionCheckWrapper.kt$ApiVersionCheckWrapper : SubsonicAPIDefinition - UnusedPrivateMember:AlbumListType.kt$AlbumListType.Companion$private operator fun String.contains(other: String) - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun areFieldsChanged(): Boolean - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun finishActivity() - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun getFields(): Boolean - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun setFields() - CommentOverPrivateFunction:EditServerFragment.kt$EditServerFragment$ private fun testConnection() - CommentOverPrivateFunction:FileLoggerTree.kt$FileLoggerTree$ private fun getNextLogFile() - CommentOverPrivateFunction:FileLoggerTree.kt$FileLoggerTree$ private fun getNumberedFile(next: Boolean) - CommentOverPrivateFunction:MediaPlayerService.kt$MediaPlayerService$ private fun buildForegroundNotification( playerState: PlayerState, currentPlaying: DownloadFile? ): Notification - CommentOverPrivateFunction:RESTMusicService.kt$RESTMusicService$ @Throws(Exception::class) private fun search2( criteria: SearchCriteria ): SearchResult - CommentOverPrivateFunction:RESTMusicService.kt$RESTMusicService$ @Throws(Exception::class) private fun searchOld( criteria: SearchCriteria ): SearchResult - CommentOverPrivateFunction:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean - CommentOverPrivateFunction:ServerRowAdapter.kt$ServerRowAdapter$ private fun serverMenuClick(view: View, position: Int) - CommentOverPrivateFunction:ServerSelectorFragment.kt$ServerSelectorFragment$ private fun editServer(index: Int) - CommentOverPrivateFunction:ServerSelectorFragment.kt$ServerSelectorFragment$ private fun onServerDeleted(index: Int) - CommentOverPrivateFunction:ServerSelectorFragment.kt$ServerSelectorFragment$ private fun setActiveServer(index: Int) - CommentOverPrivateFunction:ServerSettingsModel.kt$ServerSettingsModel$ private fun loadServerSettingFromPreferences( preferenceId: Int, serverId: Int, settings: SharedPreferences ): ServerSetting? - CommentOverPrivateFunction:ServerSettingsModel.kt$ServerSettingsModel$ private suspend fun areIndexesMissing(): Boolean - CommentOverPrivateFunction:ServerSettingsModel.kt$ServerSettingsModel$ private suspend fun reindexSettings() ComplexCondition:DownloadHandler.kt$DownloadHandler.<no name provided>$!append && !playNext && !unpin && !background ComplexCondition:FilePickerAdapter.kt$FilePickerAdapter$currentDirectory.absolutePath == "/" || currentDirectory.absolutePath == "/storage" || currentDirectory.absolutePath == "/storage/emulated" || currentDirectory.absolutePath == "/mnt" ComplexCondition:LocalMediaPlayer.kt$LocalMediaPlayer$Util.getGaplessPlaybackPreference() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && ( playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED ) @@ -89,7 +29,6 @@ ImplicitDefaultLocale:SongView.kt$SongView$String.format("%02d.", trackNumber) ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s ", bitRate) ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s > %s", suffix, transcodedSuffix) - LargeClass:MediaPlayerService.kt$MediaPlayerService : Service LargeClass:RESTMusicService.kt$RESTMusicService : MusicService LargeClass:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment LongMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() @@ -104,25 +43,11 @@ LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateDisplay(refresh: Boolean) LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory) - LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, append: Boolean, save: Boolean, autoPlay: Boolean, playNext: Boolean, shuffle: Boolean, songs: List<MusicDirectory.Entry?> ) - LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) - LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, id: String, name: String?, save: Boolean, append: Boolean, autoplay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean ) - LongParameterList:DownloadHandler.kt$DownloadHandler$( fragment: Fragment, id: String?, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, private var data: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) ) MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192 - MagicNumber:AudioFocusHandler.kt$AudioFocusHandler$0.1f - MagicNumber:DownloadFile.kt$DownloadFile$100 MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$10 MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$1000L MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$60 - MagicNumber:DownloadHandler.kt$DownloadHandler$500 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$100 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$3 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$4 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$5 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$6 - MagicNumber:FileLoggerTree.kt$FileLoggerTree$7 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer$100 MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer$1000 MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$1000 MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$60000 @@ -134,13 +59,10 @@ MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8L MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$5000L MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$50L - MagicNumber:MediaPlayerService.kt$MediaPlayerService$100 MagicNumber:MediaPlayerService.kt$MediaPlayerService$1000 MagicNumber:MediaPlayerService.kt$MediaPlayerService$256 MagicNumber:MediaPlayerService.kt$MediaPlayerService$3 MagicNumber:MediaPlayerService.kt$MediaPlayerService$4 - MagicNumber:MediaPlayerService.kt$MediaPlayerService.Companion$19 - MagicNumber:MediaPlayerService.kt$MediaPlayerService.Companion$50L MagicNumber:RESTMusicService.kt$RESTMusicService$206 MagicNumber:RESTMusicService.kt$RESTMusicService$5 MagicNumber:SongView.kt$SongView$3 @@ -159,16 +81,12 @@ ReturnCount:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onOptionsItemSelected(item: MenuItem): Boolean - SpreadOperator:MediaPlayerService.kt$MediaPlayerService$(*compactActions) - SwallowedException:DownloadFile.kt$DownloadFile$catch (e: Exception) { Timber.w("Failed to set last-modified date on %s", file) } - SwallowedException:DownloadFile.kt$DownloadFile$catch (ex: IOException) { Timber.w("Failed to rename file %s to %s", completeFile, saveFile) } SwallowedException:LocalMediaPlayer.kt$LocalMediaPlayer$catch (e: Throwable) { // Froyo or lower } SwallowedException:LocalMediaPlayer.kt$LocalMediaPlayer$catch (e: Throwable) { } SwallowedException:MediaPlayerService.kt$MediaPlayerService$catch (x: IndexOutOfBoundsException) { // Ignored } SwallowedException:NavigationActivity.kt$NavigationActivity$catch (e: Resources.NotFoundException) { destination.id.toString() } ThrowsCount:ApiCallResponseChecker.kt$ApiCallResponseChecker.Companion$@Throws(SubsonicRESTException::class, IOException::class) fun checkResponseSuccessful(response: Response<out SubsonicResponse>) TooGenericExceptionCaught:DownloadFile.kt$DownloadFile$e: Exception - TooGenericExceptionCaught:DownloadFile.kt$DownloadFile.DownloadTask$x: Exception TooGenericExceptionCaught:FileLoggerTree.kt$FileLoggerTree$x: Throwable TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$e: Throwable TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$ex: Exception @@ -185,10 +103,8 @@ TooManyFunctions:MediaPlayerService.kt$MediaPlayerService : Service TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService TooManyFunctions:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment - TopLevelPropertyNaming:SubsonicUncaughtExceptionHandler.kt$private const val filename = "ultrasonic-stacktrace.txt" UnusedPrivateMember:RESTMusicService.kt$RESTMusicService.Companion$private const val INDEXES_FOLDER_STORAGE_NAME = "indexes_folder" UtilityClassWithPublicConstructor:CommunicationErrorHandler.kt$CommunicationErrorHandler UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle - UnnecessaryAbstractClass:BaseStorageTest.kt$BaseStorageTest diff --git a/detekt-config.yml b/detekt-config.yml index f152b9ec..6b7db639 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -62,13 +62,15 @@ style: maxLineLength: 120 excludePackageStatements: false excludeImportStatements: false + MagicNumber: + ignoreNumbers: ['-1', '0', '1', '2', '100'] + ignoreEnums: true + ignorePropertyDeclaration: true + UnnecessaryAbstractClass: + active: false comments: active: true - CommentOverPrivateFunction: - active: true - CommentOverPrivateProperty: - active: true UndocumentedPublicClass: active: false searchInNestedClass: true diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt index 0aa2362f..c6b701e9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/log/FileLoggerTree.kt @@ -13,6 +13,7 @@ import timber.log.Timber * A Timber Tree which can be used to log to a file * Subclass of the DebugTree so it inherits the Tag handling */ +@Suppress("MagicNumber") class FileLoggerTree : Timber.DebugTree() { private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt index 1a70cd77..3ccdd06c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt @@ -19,6 +19,7 @@ import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.io.RandomAccessFile +import org.koin.core.component.KoinApiExtension import org.koin.java.KoinJavaComponent.inject import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService @@ -34,6 +35,7 @@ import timber.log.Timber * @author Sindre Mehus * @version $Id$ */ +@KoinApiExtension class DownloadFile( private val context: Context, val song: MusicDirectory.Entry, @@ -190,8 +192,8 @@ class DownloadFile( } completeWhenDone = false } - } catch (ex: IOException) { - Timber.w("Failed to rename file %s to %s", completeFile, saveFile) + } catch (e: IOException) { + Timber.w(e, "Failed to rename file %s to %s", completeFile, saveFile) } } @@ -199,6 +201,8 @@ class DownloadFile( return String.format("DownloadFile (%s)", song) } + @KoinApiExtension + @Suppress("TooGenericExceptionCaught") private inner class DownloadTask : CancellableTask() { override fun execute() { var inputStream: InputStream? = null @@ -286,7 +290,7 @@ class DownloadFile( Util.renameFile(partialFile, completeFile) } } - } catch (x: Exception) { + } catch (e: Exception) { Util.close(outputStream) Util.delete(completeFile) Util.delete(saveFile) @@ -295,7 +299,7 @@ class DownloadFile( if (retryCount > 0) { --retryCount } - Timber.w(x, "Failed to download '%s'.", song) + Timber.w(e, "Failed to download '%s'.", song) } } finally { Util.close(inputStream) @@ -332,8 +336,8 @@ class DownloadFile( val size = Util.getMinDisplayMetric(context) musicService.getCoverArt(song, size, true, true) } - } catch (x: Exception) { - Timber.e(x, "Failed to get cover art.") + } catch (e: Exception) { + Timber.e(e, "Failed to get cover art.") } } @@ -376,7 +380,7 @@ class DownloadFile( raf.setLength(length) raf.close() } catch (e: Exception) { - Timber.w("Failed to set last-modified date on %s", file) + Timber.w(e, "Failed to set last-modified date on %s", file) } } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt index 1d7bda38..325ecf0a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt @@ -47,6 +47,7 @@ import timber.log.Timber * Android Foreground Service for playing music * while the rest of the Ultrasonic App is in the background. */ +@Suppress("LargeClass") class MediaPlayerService : Service() { private val binder: IBinder = SimpleServiceBinder(this) private val scrobbler = Scrobbler() @@ -596,6 +597,7 @@ class MediaPlayerService : Service() { /** * This method builds a notification, reusing the Notification Builder if possible */ + @Suppress("SpreadOperator") private fun buildForegroundNotification( playerState: PlayerState, currentPlaying: DownloadFile? @@ -893,6 +895,7 @@ class MediaPlayerService : Service() { mediaSession?.setMediaButtonReceiver(null) } + @Suppress("MagicNumber") companion object { private const val NOTIFICATION_CHANNEL_ID = "org.moire.ultrasonic" private const val NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service" diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt index ac1f8c6c..487e552a 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt @@ -19,6 +19,7 @@ import org.moire.ultrasonic.util.Util /** * Retrieves a list of songs and adds them to the now playing list */ +@Suppress("LongParameterList") @KoinApiExtension class DownloadHandler( val mediaPlayerController: MediaPlayerController, diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SubsonicUncaughtExceptionHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SubsonicUncaughtExceptionHandler.kt index 7e7fab3e..5a5d77cf 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SubsonicUncaughtExceptionHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SubsonicUncaughtExceptionHandler.kt @@ -6,8 +6,6 @@ import java.io.File import java.io.PrintWriter import timber.log.Timber -private const val filename = "ultrasonic-stacktrace.txt" - /** * Logs the stack trace of uncaught exceptions to a file on the SD card. */ @@ -22,7 +20,7 @@ class SubsonicUncaughtExceptionHandler( var printWriter: PrintWriter? = null try { - file = File(FileUtil.getUltrasonicDirectory(), filename) + file = File(FileUtil.getUltrasonicDirectory(), STACKTRACE_NAME) printWriter = PrintWriter(file) val logMessage = String.format( "Android API level: %s\nUltrasonic version name: %s\n" + @@ -40,4 +38,8 @@ class SubsonicUncaughtExceptionHandler( defaultHandler?.uncaughtException(thread, throwable) } } + + companion object { + private const val STACKTRACE_NAME = "ultrasonic-stacktrace.txt" + } } From 3a05f309071b5dd542c576ebb9698ae54788d647 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 18:37:54 +0200 Subject: [PATCH 05/14] Fix more warnings --- .../ultrasonic/api/subsonic/CommonFunctions.kt | 9 ++++----- ultrasonic/build.gradle | 2 +- ultrasonic/src/main/assets/html/fr/index.html | 2 +- .../moire/ultrasonic/fragment/AboutFragment.java | 2 +- .../service/MediaPlayerController.java | 2 -- .../org/moire/ultrasonic/util/CacheCleaner.java | 16 +--------------- .../util/EntryByDiscAndTrackComparator.java | 2 +- .../org/moire/ultrasonic/util/MergeAdapter.java | 2 +- .../ultrasonic/util/SackOfViewsAdapter.java | 2 +- .../org/moire/ultrasonic/view/ArtistAdapter.java | 4 ++-- .../moire/ultrasonic/view/AutoRepeatButton.java | 1 - .../org/moire/ultrasonic/view/GenreAdapter.java | 4 ++-- .../ultrasonic/view/PodcastsChannelsAdapter.java | 2 +- .../moire/ultrasonic/view/SongListAdapter.java | 2 +- 14 files changed, 17 insertions(+), 35 deletions(-) diff --git a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt index 8414a0ab..68f4f7dd 100644 --- a/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt +++ b/core/subsonic-api/src/integrationTest/kotlin/org/moire/ultrasonic/api/subsonic/CommonFunctions.kt @@ -22,11 +22,10 @@ val CLIENT_VERSION = SubsonicAPIVersions.V1_16_0 const val CLIENT_ID = "test-client" val dateFormat by lazy( - LazyThreadSafetyMode.NONE, - { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US) - } -) + LazyThreadSafetyMode.NONE +) { + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US) +} fun MockWebServerRule.enqueueResponse(resourceName: String) { mockWebServer.enqueueResponse(resourceName) diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 53a2929e..1b4de423 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -47,7 +47,7 @@ android { lintOptions { baselineFile file("lint-baseline.xml") - warning 'MissingTranslation' + ignore 'MissingTranslation' warning 'ImpliedQuantity' abortOnError true } diff --git a/ultrasonic/src/main/assets/html/fr/index.html b/ultrasonic/src/main/assets/html/fr/index.html index 81334bf9..b786767d 100644 --- a/ultrasonic/src/main/assets/html/fr/index.html +++ b/ultrasonic/src/main/assets/html/fr/index.html @@ -20,7 +20,7 @@

- Par défaut, cette application n'est pas configurée. Après avoir configuré votre + Par défaut, cette application n'est pas configurée. Après avoir configuré votre serveur personnel, veuillez accéder aux Paramètres et modifier la configuration afin de vous connecter à votre propre ordinateur ou vos appareils mobiles.

diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/AboutFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/AboutFragment.java index ad397631..dd8d735d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/AboutFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/AboutFragment.java @@ -21,7 +21,7 @@ import org.moire.ultrasonic.R; import org.moire.ultrasonic.util.Util; /** - * Displays online help and about information in a webWiew + * Displays online help and about information in a WebView */ public class AboutFragment extends Fragment { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java index de94b05a..4384ad90 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java @@ -18,8 +18,6 @@ */ package org.moire.ultrasonic.service; -import org.moire.ultrasonic.audiofx.EqualizerController; -import org.moire.ultrasonic.audiofx.VisualizerController; import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.domain.PlayerState; import org.moire.ultrasonic.domain.RepeatMode; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java index 58059173..a61e0946 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java @@ -24,11 +24,6 @@ import kotlin.Lazy; import static org.koin.java.KoinJavaComponent.inject; -/** - * @author Sindre Mehus - * @version $Id$ - */ - /** * Responsible for cleaning up files from the offline download cache on the filesystem */ @@ -207,17 +202,8 @@ public class CacheCleaner @Override public int compare(File a, File b) { - if (a.lastModified() < b.lastModified()) - { - return -1; - } + return Long.compare(a.lastModified(), b.lastModified()); - if (a.lastModified() > b.lastModified()) - { - return 1; - } - - return 0; } }); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/EntryByDiscAndTrackComparator.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/EntryByDiscAndTrackComparator.java index bc24dfa6..cbf91c91 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/EntryByDiscAndTrackComparator.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/EntryByDiscAndTrackComparator.java @@ -47,7 +47,7 @@ public class EntryByDiscAndTrackComparator implements Comparator b ? 1 : 0; + return Long.compare(a, b); } private static int compare(String a, String b) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/MergeAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/MergeAdapter.java index 834cee30..7d8880be 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/MergeAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/MergeAdapter.java @@ -1,4 +1,4 @@ -/*** +/** Copyright (c) 2008-2009 CommonsWare, LLC Portions (c) 2009 Google, Inc. diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/SackOfViewsAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/SackOfViewsAdapter.java index 22d7ed66..080e1430 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/SackOfViewsAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/SackOfViewsAdapter.java @@ -1,4 +1,4 @@ -/*** +/** Copyright (c) 2008-2009 CommonsWare, LLC Portions (c) 2009 Google, Inc. diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ArtistAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ArtistAdapter.java index b1a7d42b..04471395 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/ArtistAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/ArtistAdapter.java @@ -68,8 +68,8 @@ public class ArtistAdapter extends ArrayAdapter implements SectionIndexe } } - sections = sectionSet.toArray(new Object[sectionSet.size()]); - positions = positionList.toArray(new Integer[positionList.size()]); + sections = sectionSet.toArray(new Object[0]); + positions = positionList.toArray(new Integer[0]); } @NonNull diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/AutoRepeatButton.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/AutoRepeatButton.java index 1d501df0..c17abcf9 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/AutoRepeatButton.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/AutoRepeatButton.java @@ -4,7 +4,6 @@ import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; -import android.widget.ImageView; import androidx.appcompat.widget.AppCompatImageView; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java index f21d827d..5b8f422e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java @@ -66,8 +66,8 @@ public class GenreAdapter extends ArrayAdapter implements SectionIndexer } } - sections = sectionSet.toArray(new Object[sectionSet.size()]); - positions = positionList.toArray(new Integer[positionList.size()]); + sections = sectionSet.toArray(new Object[0]); + positions = positionList.toArray(new Integer[0]); } @NonNull diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcastsChannelsAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcastsChannelsAdapter.java index 28ac4763..b821a197 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcastsChannelsAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/PodcastsChannelsAdapter.java @@ -32,7 +32,7 @@ public class PodcastsChannelsAdapter extends ArrayAdapter { PodcastsChannel entry = getItem(position); TextView view; - if (convertView != null && convertView instanceof PlaylistView) { + if (convertView instanceof PlaylistView) { view = (TextView) convertView; } else { view = (TextView) layoutInflater diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongListAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongListAdapter.java index 1844e4d2..16faf837 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongListAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongListAdapter.java @@ -28,7 +28,7 @@ public class SongListAdapter extends ArrayAdapter SongView view; - if (convertView != null && convertView instanceof SongView) + if (convertView instanceof SongView) { SongView currentView = (SongView) convertView; if (currentView.getEntry().equals(entry)) From 10edce9f56055ce228936a1c798be4215c4a06d6 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 22:34:30 +0200 Subject: [PATCH 06/14] Disable daemon --- .circleci/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 36bef6a5..26b002d2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: key: gradle-cache-{{ checksum "dependencies.gradle" }} - run: name: clean gradle.properties - command: echo -e "android.useAndroidX=true\nandroid.enableJetifier=true\n" > gradle.properties + command: echo -e "android.useAndroidX=true\nandroid.enableJetifier=true\norg.gradle.daemon=false\n" > gradle.properties - run: name: checkstyle command: ./gradlew -Pqc ktlintCheck @@ -27,6 +27,10 @@ jobs: command: | ./gradlew ciTest testDebugUnitTest ./gradlew jacocoFullReport + - save_cache: + paths: + - ~/.gradle + key: gradle-cache-{{ checksum "dependencies.gradle" }} - run: name: lint command: ./gradlew :ultrasonic:lintRelease From dff054847adcc28e937740c90c6af16b36c815c3 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 18:58:12 +0200 Subject: [PATCH 07/14] Regenerate lint baseline Removes 238 fixed issues Adds 177 previously unlisted issues --- ultrasonic/lint-baseline.xml | 3507 +++++++++++++++++----------------- 1 file changed, 1771 insertions(+), 1736 deletions(-) diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index 8347a5e7..c80cca17 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -1,114 +1,15 @@ - + + id="ScopedStorage" + message="WRITE_EXTERNAL_STORAGE no longer provides write access when targeting Android 10, unless you use `requestLegacyExternalStorage`" + errorLine1=" <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + file="src/main/AndroidManifest.xml" + line="9" + column="36"/> - - - - - - - - @@ -162,7 +41,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -173,7 +52,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -184,7 +63,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -195,7 +74,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -206,98 +85,10 @@ errorLine2=" ~~~~~~~~~~~"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -316,10 +107,31 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -694,10 +260,109 @@ errorLine2=" ^"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -716,7 +381,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -727,7 +392,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -738,24 +403,101 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + errorLine1=" Random random = new java.security.SecureRandom();" + errorLine2=" ~~~~~~~~~~~~"> + file="src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java" + line="653" + column="37"/> + message="Using `setJavaScriptEnabled` can introduce XSS vulnerabilities into your application, review carefully" + errorLine1=" webView.getSettings().setJavaScriptEnabled(true);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/org/moire/ultrasonic/fragment/AboutFragment.java" + line="51" + column="9"/> + + + + + + + + - - - - - - - - @@ -829,17 +579,6 @@ file="src/main/res/drawable-xhdpi-v14"/> - - - - @@ -858,7 +597,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -869,32 +608,10 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - - - - - @@ -913,85 +630,19 @@ errorLine2=" ~~~~~~~~~~~~~~~"> + message="Do not place Android context classes in static fields; this is a memory leak" + errorLine1=" private static SeekBar progressBar; // TODO: Refactor this to not be static" + errorLine2=" ~~~~~~"> - - - - - - - - - - - - - - - - - - - - - - - - + file="src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java" + line="103" + column="13"/> @@ -1016,9 +667,20 @@ column="2"/> + + + + + message="Possible overdraw: Root element paints background `?android:attr/selectableItemBackground` with a theme that also paints a background (inferred theme is `@style/NoActionBar`)" + errorLine1=" a:background="?android:attr/selectableItemBackground"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + id="UnusedResources" + message="The resource `R.drawable.btn_bg` appears to be unused" + errorLine1=" <item android:drawable="@color/ics_opaque" android:state_pressed="true"/>" + errorLine2="^"> + file="src/main/res/drawable/btn_bg.xml" + line="14" + column="1"/> + + + + - - - - - - - - - - - - + message="The resource `R.drawable.line` appears to be unused" + errorLine1="<shape xmlns:android="http://schemas.android.com/apk/res/android"" + errorLine2="^"> + file="src/main/res/drawable/line.xml" + line="2" + column="1"/> - - - - - - - - - - - - - + message="The resource `R.drawable.menu_arrow` appears to be unused" + errorLine1="<vector xmlns:android="http://schemas.android.com/apk/res/android"" + errorLine2="^"> + file="src/main/res/drawable/menu_arrow.xml" + line="1" + column="1"/> + message="The resource `R.string.main_shuffle` appears to be unused" + errorLine1=" <string name="main.shuffle">Shuffle Play</string>" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + file="src/main/res/values/strings.xml" + line="109" + column="13"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1242,7 +1005,11 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + + + + + + + + + + + + + + + + @@ -1285,7 +1119,11 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1328,7 +1642,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1339,7 +1653,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1350,7 +1664,11 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + @@ -1393,7 +1723,11 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + @@ -1436,39 +1778,55 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + @@ -1479,39 +1837,51 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + @@ -1522,52 +1892,77 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + message="The resource `R.plurals.select_album_donate_dialog_n_trial_days_left` appears to be unused" + errorLine1=" <plurals name="select_album_donate_dialog_n_trial_days_left">" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/res/values/strings.xml" + line="490" + column="14"/> + + + + + + + + + + message="The resource `R.drawable.thumb` appears to be unused" + errorLine1="<shape xmlns:android="http://schemas.android.com/apk/res/android"" + errorLine2="^"> + file="src/main/res/drawable/thumb.xml" + line="2" + column="1"/> + message="The resource `R.drawable.thumb_drawable` appears to be unused" + errorLine1="<selector xmlns:android="http://schemas.android.com/apk/res/android">" + errorLine2="^"> - - - - + file="src/main/res/drawable/thumb_drawable.xml" + line="2" + column="1"/> @@ -1598,7 +1993,40 @@ errorLine1=" <string name="util.no_time">-:--</string>" errorLine2=" ^"> + + + + + + + + + + + + @@ -1610,29 +2038,7 @@ errorLine2=" ^"> - - - - - - - - @@ -1643,7 +2049,29 @@ errorLine2=" ^"> + + + + + + + + @@ -1654,7 +2082,7 @@ errorLine2=" ^"> @@ -1665,7 +2093,7 @@ errorLine2=" ^"> @@ -1676,100 +2104,26 @@ errorLine2=" ^"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + message="The following unrelated icon files have identical contents: list_pressed_holo_dark.9.png, list_pressed_holo_light.9.png"> + + + + @@ -1843,7 +2197,7 @@ errorLine2=" ~~~~~~~~~"> @@ -1854,63 +2208,74 @@ errorLine2=" ^"> + message="`onTouch` lambda should call `View#performClick` when a click is detected" + errorLine1=" getView().setOnTouchListener((v, event) -> handleOnTouch(event));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/org/moire/ultrasonic/fragment/NowPlayingFragment.java" + line="132" + column="42"/> + errorLine1=" albumArtImageView.setOnTouchListener(new View.OnTouchListener()" + errorLine2=" ^"> - - - - + file="src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java" + line="241" + column="9"/> + errorLine1=" public boolean onTouch(View view, MotionEvent me)" + errorLine2=" ~~~~~~~"> + file="src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java" + line="244" + column="28"/> + message="Custom view ``VisualizerView`` has `setOnTouchListener` called on it but does not override `performClick`" + errorLine1=" visualizerView.setOnTouchListener(new View.OnTouchListener()" + errorLine2=" ^"> + file="src/main/java/org/moire/ultrasonic/fragment/PlayerFragment.java" + line="531" + column="21"/> + + + + + + + + @@ -1942,7 +2307,7 @@ errorLine2=" ~~~~~~~~~"> @@ -1953,7 +2318,7 @@ errorLine2=" ~~~~~~~~~"> @@ -1964,76 +2329,87 @@ errorLine2=" ~~~~~~~~~"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - @@ -2249,18 +2625,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> - - - - @@ -2271,8 +2636,8 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -2282,7 +2647,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -2293,8 +2658,8 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -2304,7 +2669,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -2315,8 +2680,8 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -2326,7 +2691,7 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -2337,8 +2702,8 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -2348,11 +2713,22 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2591,7 +2813,18 @@ errorLine2=" ~~~~~~~~~"> + + + + @@ -2601,8 +2834,8 @@ errorLine1=" <ImageView" errorLine2=" ~~~~~~~~~"> @@ -2628,6 +2861,17 @@ column="6"/> + + + + @@ -2646,7 +2890,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2657,7 +2901,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2668,7 +2912,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2679,7 +2923,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2690,7 +2934,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2701,7 +2945,7 @@ errorLine2=" ~~~~~~~~~"> @@ -2749,39 +2993,6 @@ column="10"/> - - - - - - - - - - - - + message="Hardcoded string "A", should use `@string` resource" + errorLine1=" a:text="A"" + errorLine2=" ~~~~~~~~~~"> @@ -2903,6 +3114,17 @@ column="13"/> + + + + + + + + + + + + + message="When you define `paddingRight` you should probably also define `paddingLeft` for right-to-left symmetry" + errorLine1=" a:paddingRight="3dip"" + errorLine2=" ~~~~~~~~~~~~~~"> + line="73" + column="9"/> + + + + + + + + + errorLine1=" a:paddingRight="3dip" />" + errorLine2=" ~~~~~~~~~~~~~~"> + file="src/main/res/layout/album_list_item_legacy.xml" + line="49" + column="9"/> + message="When you define `paddingStart` you should probably also define `paddingEnd` for right-to-left symmetry" + errorLine1=" a:paddingStart="16dp"" + errorLine2=" ~~~~~~~~~~~~~~"> + file="src/main/res/layout/navigation_header.xml" + line="35" + column="13"/> + + + + + + + + - - - - - - - - @@ -3148,11 +3414,11 @@ + errorLine1=" a:paddingLeft="4dp"/>" + errorLine2=" ~~~~~~~~~~~~~"> @@ -3163,7 +3429,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -3174,18 +3440,7 @@ errorLine2=" ~~~~~~~~~~~~~"> - - - - @@ -3261,7 +3516,7 @@ errorLine1=" a:paddingLeft="6dp"" errorLine2=" ~~~~~~~~~~~~~"> @@ -3284,7 +3539,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -3295,18 +3550,18 @@ errorLine2=" ~~~~~~~~~~~~~"> + message="When you define `paddingRight` you should probably also define `paddingLeft` for right-to-left symmetry" + errorLine1=" a:paddingRight="4dip"/>" + errorLine2=" ~~~~~~~~~~~~~~"> @@ -3317,18 +3572,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> - - - - @@ -3357,67 +3601,67 @@ + errorLine1=" a:layout_gravity="left|center_vertical"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + file="src/main/res/layout/album_list_item_legacy.xml" + line="12" + column="27"/> + errorLine1=" a:paddingLeft="3dip" />" + errorLine2=" ~~~~~~~~~~~~~"> + file="src/main/res/layout/album_list_item_legacy.xml" + line="13" + column="9"/> + errorLine1=" a:layout_gravity="left|center_vertical"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + file="src/main/res/layout/album_list_item_legacy.xml" + line="20" + column="27"/> + errorLine1=" a:paddingLeft="6dip"" + errorLine2=" ~~~~~~~~~~~~~"> + + + + + column="9"/> + errorLine1=" a:paddingRight="3dip" />" + errorLine2=" ~~~~~~~~~~~~~~"> - - - - + file="src/main/res/layout/album_list_item_legacy.xml" + line="49" + column="9"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + message="Consider adding `a:layout_alignParentStart="true"` to better support right-to-left layouts" + errorLine1=" a:layout_alignParentLeft="true"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -4131,7 +4056,7 @@ errorLine2=" ~~~~"> @@ -4142,18 +4067,150 @@ errorLine2=" ~~~~~~~~~~~~"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4170,12 +4227,12 @@ + message="Consider adding `a:paddingStart="4dp"` to better support right-to-left layouts" + errorLine1=" a:paddingLeft="4dp"/>" + errorLine2=" ~~~~~~~~~~~~~"> @@ -4186,7 +4243,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -4197,18 +4254,7 @@ errorLine2=" ~~~~~~~~~~~~~"> - - - - @@ -4372,7 +4418,7 @@ errorLine1=" a:paddingLeft="6dp"" errorLine2=" ~~~~~~~~~~~~~"> @@ -4383,8 +4429,8 @@ errorLine1=" a:layout_marginLeft="10dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -4394,8 +4440,8 @@ errorLine1=" a:layout_marginLeft="10dip"" errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -4432,6 +4478,17 @@ column="13"/> + + + + @@ -4450,7 +4507,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -4461,7 +4518,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -4472,7 +4529,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -4483,7 +4540,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -4494,7 +4551,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -4505,7 +4562,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -4516,7 +4573,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -4527,7 +4584,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -4538,7 +4595,7 @@ errorLine2=" ~~~~~~~~~~~~~"> @@ -4549,18 +4606,18 @@ errorLine2=" ~~~~~~~~~~~~~~"> + message="Consider adding `a:paddingEnd="4dip"` to better support right-to-left layouts" + errorLine1=" a:paddingRight="4dip"/>" + errorLine2=" ~~~~~~~~~~~~~~"> @@ -4571,7 +4628,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -4582,29 +4639,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> - - - - - - - - From d26f8595abc197b47f19c1fe0e642281f146de7d Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 20:01:09 +0200 Subject: [PATCH 08/14] Manually disable IconMissingDensityFolder --- ultrasonic/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 1b4de423..21497c6d 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -49,6 +49,7 @@ android { baselineFile file("lint-baseline.xml") ignore 'MissingTranslation' warning 'ImpliedQuantity' + disable 'IconMissingDensityFolder' abortOnError true } From 387119a90ca7a3cec9afe1c39fb186e71bfb2c4d Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 19:01:29 +0200 Subject: [PATCH 09/14] Make the CI fail if any new lint issues (warnings or errors) are added. --- ultrasonic/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 21497c6d..01f5c64f 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -51,6 +51,7 @@ android { warning 'ImpliedQuantity' disable 'IconMissingDensityFolder' abortOnError true + warningsAsErrors true } kotlinOptions { From 78e4a00476d772f7090075b347ee459685d618f6 Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 19:38:51 +0200 Subject: [PATCH 10/14] Our target sdk is 29 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 26b002d2..c1714f90 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 3 jobs: build: docker: - - image: circleci/android:api-28 + - image: circleci/android:api-29 working_directory: ~/ultrasonic environment: JVM_OPTS: -Xmx3200m From 29a98c1263f8726a20c1ef20d770177fc37febdc Mon Sep 17 00:00:00 2001 From: tzugen Date: Wed, 19 May 2021 19:18:24 +0200 Subject: [PATCH 11/14] The AGP build cache was removed in AGP 4.1. Previously introduced in AGP 2.3 to complement the Gradle build cache, the AGP build cache was superseded entirely by the Gradle build cache in AGP 4.1. This change does not impact build time. --- gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4e35a3c1..19077ae1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,6 +8,5 @@ kotlin.incremental=true kotlin.caching.enabled=true kotlin.incremental.usePreciseJavaTracking=true -android.enableBuildCache=true android.useAndroidX=true android.enableJetifier=true From ab4c2c1bbe739270f76780fe67ccf19cf7767906 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 20 May 2021 07:40:07 +0200 Subject: [PATCH 12/14] Add staggered caching, to increase the times we have a warm cache --- .circleci/config.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c1714f90..563e9754 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,10 @@ jobs: steps: - checkout - restore_cache: - key: gradle-cache-{{ checksum "dependencies.gradle" }} + keys: + - v1-ultrasonic-{{ .Branch }}-{{ checksum "dependencies.gradle" }} + - v1-ultrasonic-{{ .Branch }} + - v1-ultrasonic - run: name: clean gradle.properties command: echo -e "android.useAndroidX=true\nandroid.enableJetifier=true\norg.gradle.daemon=false\n" > gradle.properties @@ -27,10 +30,6 @@ jobs: command: | ./gradlew ciTest testDebugUnitTest ./gradlew jacocoFullReport - - save_cache: - paths: - - ~/.gradle - key: gradle-cache-{{ checksum "dependencies.gradle" }} - run: name: lint command: ./gradlew :ultrasonic:lintRelease @@ -40,7 +39,7 @@ jobs: - save_cache: paths: - ~/.gradle - key: gradle-cache-{{ checksum "dependencies.gradle" }} + key: v1-ultrasonic-{{ .Branch }}-{{ checksum "dependencies.gradle" }} - store_artifacts: path: ultrasonic/build/reports destination: reports @@ -76,7 +75,10 @@ jobs: steps: - checkout - restore_cache: - key: gradle-cache-{{ checksum "dependencies.gradle" }} + keys: + - v1-ultrasonic-{{ .Branch }}-{{ checksum "dependencies.gradle" }} + - v1-ultrasonic-{{ .Branch }} + - v1-ultrasonic - run: name: decrypt ultrasonic-keystore command: openssl aes-256-cbc -K ${ULTRASONIC_KEYSTORE_KEY} -iv ${ULTRASONIC_KEYSTORE_IV} -in ultrasonic-keystore.enc -out ultrasonic-keystore -d From b0b03c0fe0e7fe53fa003d80ceba0cae832c9df8 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 20 May 2021 08:12:29 +0200 Subject: [PATCH 13/14] Be more selective with the configuration of gradle.properties in CI --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 563e9754..ae34f820 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,8 +14,10 @@ jobs: - v1-ultrasonic-{{ .Branch }} - v1-ultrasonic - run: - name: clean gradle.properties - command: echo -e "android.useAndroidX=true\nandroid.enableJetifier=true\norg.gradle.daemon=false\n" > gradle.properties + name: configure gradle.properties for CI building + command: | + sed -i '/^org.gradle.jvmargs/d' gradle.properties + sed -i 's/^org.gradle.daemon=true/org.gradle.daemon=false/g' gradle.properties - run: name: checkstyle command: ./gradlew -Pqc ktlintCheck From f1713fdbd34e277dda1bfc4a0ef6e06039ff4a99 Mon Sep 17 00:00:00 2001 From: tzugen Date: Thu, 20 May 2021 18:41:15 +0200 Subject: [PATCH 14/14] Use a recently maintained version of DSLV, which compiles correctly with newer Gradles. --- build.gradle | 1 + dependencies.gradle | 4 ++-- ultrasonic/src/main/res/layout/current_playlist.xml | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index f919138c..597d384a 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ allprojects { repositories { mavenCentral() google() + maven { url 'https://jitpack.io' } } } diff --git a/dependencies.gradle b/dependencies.gradle index 9dc6ce74..65294704 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -31,7 +31,7 @@ ext.versions = [ twitterSerial : "0.1.6", koin : "2.2.2", picasso : "2.71828", - sortListView : "1.0", + sortListView : "1.0.1", junit4 : "4.13.2", junit5 : "5.7.1", @@ -90,7 +90,7 @@ ext.other = [ dexter : "com.karumi:dexter:$versions.dexter", timber : "com.jakewharton.timber:timber:$versions.timber", fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll", - sortListView : "asia.ivity.android:drag-sort-listview:$versions.sortListView", + sortListView : "com.github.tzugen:drag-sort-listview:$versions.sortListView", ] ext.testing = [ diff --git a/ultrasonic/src/main/res/layout/current_playlist.xml b/ultrasonic/src/main/res/layout/current_playlist.xml index 63d1f73e..1b933376 100644 --- a/ultrasonic/src/main/res/layout/current_playlist.xml +++ b/ultrasonic/src/main/res/layout/current_playlist.xml @@ -27,6 +27,7 @@ app:remove_mode="flingRemove" app:fling_handle_id="@+id/song_drag" app:drag_start_mode="onMove" - app:float_background_color="?attr/color_background" /> + app:float_background_color="?attr/color_background" + app:float_alpha="0.7" /> \ No newline at end of file