From 209e4c87b0edcb597888ed304699941b15600865 Mon Sep 17 00:00:00 2001 From: daktak Date: Tue, 12 Jul 2016 09:33:59 +1000 Subject: [PATCH 1/3] update to add header --- app/build.gradle | 4 ++-- app/src/main/java/org/afhdownloader/Download.java | 8 ++++++-- app/src/main/java/org/afhdownloader/MainActivity.java | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 64fd03c..93f2ff0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.afhdownloader" minSdkVersion 16 targetSdkVersion 24 - versionCode 4 - versionName "0.3" + versionCode 5 + versionName "0.3.1" } buildTypes { release { diff --git a/app/src/main/java/org/afhdownloader/Download.java b/app/src/main/java/org/afhdownloader/Download.java index 294b31f..2e4c439 100644 --- a/app/src/main/java/org/afhdownloader/Download.java +++ b/app/src/main/java/org/afhdownloader/Download.java @@ -94,6 +94,10 @@ public String parseUrl(String url) { urls.add(link.ownText()); urls.add(link.attr("href")); } + //set title + Elements h1s = doc.select("h1"); + String h1 = h1s.get(0).ownText(); + urls.add(h1); } catch (Throwable t) { Log.e(LOGTAG,t.getMessage()); @@ -253,9 +257,9 @@ protected void onPostExecute(String s) { public void download(String url, String desc, String title, String filename) { SharedPreferences mySharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - String exten = "zip"; + String exten = "/"; - if (url.endsWith(exten)) { + if (!url.endsWith(exten)) { Log.d(LOGTAG, "Downloading: " + url); boolean external = mySharedPreferences.getBoolean("prefExternal", false); diff --git a/app/src/main/java/org/afhdownloader/MainActivity.java b/app/src/main/java/org/afhdownloader/MainActivity.java index 19cee2e..55b574c 100644 --- a/app/src/main/java/org/afhdownloader/MainActivity.java +++ b/app/src/main/java/org/afhdownloader/MainActivity.java @@ -262,9 +262,10 @@ public void setList(List values) { } } + getSupportActionBar().setTitle(values.get(values.size()-1)); //for each returned value - filename and url - for (int j = 0; j < values.size(); j+=2) { + for (int j = 0; j < values.size()-1; j+=2) { String md5val = ""; String url = values.get(j+1).trim(); String name = values.get(j).trim(); From c59cc7d00508626edda2cb9b08c914d78981cd88 Mon Sep 17 00:00:00 2001 From: daktak Date: Tue, 12 Jul 2016 12:51:08 +1000 Subject: [PATCH 2/3] Swipe to delete added --- app/build.gradle | 2 +- .../java/org/afhdownloader/MainActivity.java | 66 ++- .../SwipeDismissListViewTouchListener.java | 414 ++++++++++++++++++ .../SwipeDismissTouchListener.java | 291 ++++++++++++ app/src/main/res/values/donottranslate.xml | 3 +- app/src/main/res/values/strings.xml | 3 + 6 files changed, 769 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/org/afhdownloader/SwipeDismissListViewTouchListener.java create mode 100644 app/src/main/java/org/afhdownloader/SwipeDismissTouchListener.java diff --git a/app/build.gradle b/app/build.gradle index 93f2ff0..4a48efb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,7 +9,7 @@ android { minSdkVersion 16 targetSdkVersion 24 versionCode 5 - versionName "0.3.1" + versionName "0.4" } buildTypes { release { diff --git a/app/src/main/java/org/afhdownloader/MainActivity.java b/app/src/main/java/org/afhdownloader/MainActivity.java index 55b574c..9251d9a 100644 --- a/app/src/main/java/org/afhdownloader/MainActivity.java +++ b/app/src/main/java/org/afhdownloader/MainActivity.java @@ -2,8 +2,10 @@ import android.Manifest; import android.app.AlarmManager; +import android.app.AlertDialog; import android.app.PendingIntent; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; @@ -39,9 +41,12 @@ public class MainActivity extends AppCompatActivity private static final int REQUEST_PREFS = 99; private static final int RC_EXT_WRITE =1; private static final int RC_EXT_READ=2; + private static final int YES_NO_CALL=80; public static MainActivity instance = null; - + public ArrayList md5check = new ArrayList(); + public ArrayList names = new ArrayList(); public ArrayList urls = new ArrayList(); + public String directory; @Override protected void onCreate(Bundle savedInstanceState) { @@ -238,12 +243,12 @@ public void writeFile(String name, String body){ } public void setList(List values) { - ArrayList names = new ArrayList(); SharedPreferences mySharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - String directory = mySharedPreferences.getString("prefDirectory",Environment.DIRECTORY_DOWNLOADS).trim(); + directory = mySharedPreferences.getString("prefDirectory",Environment.DIRECTORY_DOWNLOADS).trim(); boolean external = mySharedPreferences.getBoolean("prefExternal",false); - ArrayList md5check = new ArrayList(); - + md5check = new ArrayList(); + String md5_ext = getString(R.string.md5_ext); + final String md5_calc_ext = getString(R.string.md5calc_ext); if (external){ directory = Environment.DIRECTORY_DOWNLOADS; } @@ -284,16 +289,16 @@ public void setList(List values) { for (int k = 0; k < file.length; k++) { if (name.equals(file[k].getName())) { - String md5 = readFile(name + ".md5"); + String md5 = readFile(name + md5_ext); if (!md5.isEmpty()) { - String md5calc = readFile(name+".calc.md5"); + String md5calc = readFile(name+md5_calc_ext); if (md5calc.isEmpty()) { md5calc = MD5.calculateMD5(file[k]); } if (md5calc.equalsIgnoreCase(md5)) { md5val = "Y"; //cache this result - writeFile(name+".calc.md5", md5calc); + writeFile(name+md5_calc_ext, md5calc); } else { md5val = "N"; //don't cache, in the event the file is still downloading @@ -322,6 +327,38 @@ public void setList(List values) { // Set the ArrayAdapter as the ListView's adapter. mainListView.setAdapter( listAdapter ); + SwipeDismissListViewTouchListener touchListener = + new SwipeDismissListViewTouchListener( + mainListView, + new SwipeDismissListViewTouchListener.DismissCallbacks() { + @Override + public boolean canDismiss(int position) { + boolean dis = true; + if (md5check.get(position).isEmpty()) { dis = false; }; + return dis; + } + + @Override + public void onDismiss(ListView listView, int[] reverseSortedPositions) { + for (int position : reverseSortedPositions) { + final int pos = position; + DialogInterface.OnClickListener yesListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + File direct = new File(Environment.getExternalStorageDirectory() + "/" + directory+"/"+names.get(pos)); + Log.d(LOGTAG, "Delete " + direct.getName()); + if (direct.exists()&&direct.isFile()) { direct.delete(); } + File md5file = new File(getFilesDir(), names.get(pos)+md5_calc_ext ); + if (md5file.exists()&&md5file.isFile()) { md5file.delete(); } + if (MainActivity.instance != null) { + run(MainActivity.instance); + } + } + }; + message_dialog_yes_no(getString(R.string.delete) + " " + names.get(pos)+"?" , yesListener); + } + } + }); + mainListView.setOnTouchListener(touchListener); mainListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override @@ -344,6 +381,19 @@ public void onItemClick(AdapterView parent, final View view, int position, lo }); } + public void message_dialog_yes_no (String msg, DialogInterface.OnClickListener yesListener) { + AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); + + builder.setMessage(msg) + .setCancelable(false) + .setPositiveButton(getString(R.string.yes), yesListener) + .setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + }}) + .show(); + } + /** * Executes commands as root user * @author http://muzikant-android.blogspot.com/2011/02/how-to-get-root-access-and-execute.html diff --git a/app/src/main/java/org/afhdownloader/SwipeDismissListViewTouchListener.java b/app/src/main/java/org/afhdownloader/SwipeDismissListViewTouchListener.java new file mode 100644 index 0000000..1fad7b9 --- /dev/null +++ b/app/src/main/java/org/afhdownloader/SwipeDismissListViewTouchListener.java @@ -0,0 +1,414 @@ +package org.afhdownloader; + +/* + * Copyright 2013 Google Inc. + * + * 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. + */ + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; +import android.graphics.Rect; +import android.os.Build; +import android.os.SystemClock; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.widget.AbsListView; +import android.widget.ListView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A {@link View.OnTouchListener} that makes the list items in a {@link ListView} + * dismissable. {@link ListView} is given special treatment because by default it handles touches + * for its list items... i.e. it's in charge of drawing the pressed state (the list selector), + * handling list item clicks, etc. + * + *

After creating the listener, the caller should also call + * {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}, passing + * in the scroll listener returned by {@link #makeScrollListener()}. If a scroll listener is + * already assigned, the caller should still pass scroll changes through to this listener. This will + * ensure that this {@link SwipeDismissListViewTouchListener} is paused during list view + * scrolling.

+ * + *

Example usage:

+ * + *
+ * SwipeDismissListViewTouchListener touchListener =
+ *         new SwipeDismissListViewTouchListener(
+ *                 listView,
+ *                 new SwipeDismissListViewTouchListener.OnDismissCallback() {
+ *                     public void onDismiss(ListView listView, int[] reverseSortedPositions) {
+ *                         for (int position : reverseSortedPositions) {
+ *                             adapter.remove(adapter.getItem(position));
+ *                         }
+ *                         adapter.notifyDataSetChanged();
+ *                     }
+ *                 });
+ * listView.setOnTouchListener(touchListener);
+ * listView.setOnScrollListener(touchListener.makeScrollListener());
+ * 
+ * + *

This class Requires API level 12 or later due to use of {@link + * ViewPropertyAnimator}.

+ * + *

For a generalized {@link View.OnTouchListener} that makes any view dismissable, + * see {@link SwipeDismissTouchListener}.

+ * + * @see SwipeDismissTouchListener + */ +public class SwipeDismissListViewTouchListener implements View.OnTouchListener { + // Cached ViewConfiguration and system-wide constant values + private int mSlop; + private int mMinFlingVelocity; + private int mMaxFlingVelocity; + private long mAnimationTime; + + // Fixed properties + private ListView mListView; + private DismissCallbacks mCallbacks; + private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero + + // Transient properties + private List mPendingDismisses = new ArrayList(); + private int mDismissAnimationRefCount = 0; + private float mDownX; + private float mDownY; + private boolean mSwiping; + private int mSwipingSlop; + private VelocityTracker mVelocityTracker; + private int mDownPosition; + private View mDownView; + private boolean mPaused; + + /** + * The callback interface used by {@link SwipeDismissListViewTouchListener} to inform its client + * about a successful dismissal of one or more list item positions. + */ + public interface DismissCallbacks { + /** + * Called to determine whether the given position can be dismissed. + */ + boolean canDismiss(int position); + + /** + * Called when the user has indicated they she would like to dismiss one or more list item + * positions. + * + * @param listView The originating {@link ListView}. + * @param reverseSortedPositions An array of positions to dismiss, sorted in descending + * order for convenience. + */ + void onDismiss(ListView listView, int[] reverseSortedPositions); + } + + /** + * Constructs a new swipe-to-dismiss touch listener for the given list view. + * + * @param listView The list view whose items should be dismissable. + * @param callbacks The callback to trigger when the user has indicated that she would like to + * dismiss one or more list items. + */ + public SwipeDismissListViewTouchListener(ListView listView, DismissCallbacks callbacks) { + ViewConfiguration vc = ViewConfiguration.get(listView.getContext()); + mSlop = vc.getScaledTouchSlop(); + mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16; + mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); + mAnimationTime = listView.getContext().getResources().getInteger( + android.R.integer.config_shortAnimTime); + mListView = listView; + mCallbacks = callbacks; + } + + /** + * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures. + * + * @param enabled Whether or not to watch for gestures. + */ + public void setEnabled(boolean enabled) { + mPaused = !enabled; + } + + /** + * Returns an {@link AbsListView.OnScrollListener} to be added to the {@link + * ListView} using {@link ListView#setOnScrollListener(AbsListView.OnScrollListener)}. + * If a scroll listener is already assigned, the caller should still pass scroll changes through + * to this listener. This will ensure that this {@link SwipeDismissListViewTouchListener} is + * paused during list view scrolling.

+ * + * @see SwipeDismissListViewTouchListener + */ + public AbsListView.OnScrollListener makeScrollListener() { + return new AbsListView.OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView absListView, int scrollState) { + setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + } + + @Override + public void onScroll(AbsListView absListView, int i, int i1, int i2) { + } + }; + } + + @TargetApi(12) + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + if (mViewWidth < 2) { + mViewWidth = mListView.getWidth(); + } + + switch (motionEvent.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + if (mPaused) { + return false; + } + + // TODO: ensure this is a finger, and set a flag + + // Find the child view that was touched (perform a hit test) + Rect rect = new Rect(); + int childCount = mListView.getChildCount(); + int[] listViewCoords = new int[2]; + mListView.getLocationOnScreen(listViewCoords); + int x = (int) motionEvent.getRawX() - listViewCoords[0]; + int y = (int) motionEvent.getRawY() - listViewCoords[1]; + View child; + for (int i = 0; i < childCount; i++) { + child = mListView.getChildAt(i); + child.getHitRect(rect); + if (rect.contains(x, y)) { + mDownView = child; + break; + } + } + + if (mDownView != null) { + mDownX = motionEvent.getRawX(); + mDownY = motionEvent.getRawY(); + mDownPosition = mListView.getPositionForView(mDownView); + if (mCallbacks.canDismiss(mDownPosition)) { + mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker.addMovement(motionEvent); + } else { + mDownView = null; + } + } + return false; + } + + case MotionEvent.ACTION_CANCEL: { + if (mVelocityTracker == null) { + break; + } + + if (mDownView != null && mSwiping) { + // cancel + mDownView.animate() + .translationX(0) + .alpha(1) + .setDuration(mAnimationTime) + .setListener(null); + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + mDownX = 0; + mDownY = 0; + mDownView = null; + mDownPosition = ListView.INVALID_POSITION; + mSwiping = false; + break; + } + + case MotionEvent.ACTION_UP: { + if (mVelocityTracker == null) { + break; + } + + float deltaX = motionEvent.getRawX() - mDownX; + mVelocityTracker.addMovement(motionEvent); + mVelocityTracker.computeCurrentVelocity(1000); + float velocityX = mVelocityTracker.getXVelocity(); + float absVelocityX = Math.abs(velocityX); + float absVelocityY = Math.abs(mVelocityTracker.getYVelocity()); + boolean dismiss = false; + boolean dismissRight = false; + if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) { + dismiss = true; + dismissRight = deltaX > 0; + } else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity + && absVelocityY < absVelocityX && mSwiping) { + // dismiss only if flinging in the same direction as dragging + dismiss = (velocityX < 0) == (deltaX < 0); + dismissRight = mVelocityTracker.getXVelocity() > 0; + } + if (dismiss && mDownPosition != ListView.INVALID_POSITION) { + // dismiss + final View downView = mDownView; // mDownView gets null'd before animation ends + final int downPosition = mDownPosition; + ++mDismissAnimationRefCount; + if (Build.VERSION.SDK_INT >= 12) { + mDownView.animate() + .translationX(dismissRight ? mViewWidth : -mViewWidth) + .alpha(0) + .setDuration(mAnimationTime) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + performDismiss(downView, downPosition); + } + }); + } else { + performDismiss(downView, downPosition); + } + } else { + // cancel + if (Build.VERSION.SDK_INT >= 12) { + mDownView.animate() + .translationX(0) + .alpha(1) + .setDuration(mAnimationTime) + .setListener(null); + } + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + mDownX = 0; + mDownY = 0; + mDownView = null; + mDownPosition = ListView.INVALID_POSITION; + mSwiping = false; + break; + } + + case MotionEvent.ACTION_MOVE: { + if (mVelocityTracker == null || mPaused) { + break; + } + + mVelocityTracker.addMovement(motionEvent); + float deltaX = motionEvent.getRawX() - mDownX; + float deltaY = motionEvent.getRawY() - mDownY; + if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) { + mSwiping = true; + mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop); + mListView.requestDisallowInterceptTouchEvent(true); + + // Cancel ListView's touch (un-highlighting the item) + MotionEvent cancelEvent = MotionEvent.obtain(motionEvent); + cancelEvent.setAction(MotionEvent.ACTION_CANCEL | + (motionEvent.getActionIndex() + << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); + mListView.onTouchEvent(cancelEvent); + cancelEvent.recycle(); + } + + if (mSwiping) { + mDownView.setTranslationX(deltaX - mSwipingSlop); + mDownView.setAlpha(Math.max(0f, Math.min(1f, + 1f - 2f * Math.abs(deltaX) / mViewWidth))); + view.performClick(); + return true; + } + break; + } + } + return false; + } + + class PendingDismissData implements Comparable { + public int position; + public View view; + + public PendingDismissData(int position, View view) { + this.position = position; + this.view = view; + } + + @Override + public int compareTo(PendingDismissData other) { + // Sort by descending position + return other.position - position; + } + } + + private void performDismiss(final View dismissView, final int dismissPosition) { + // Animate the dismissed list item to zero-height and fire the dismiss callback when + // all dismissed list item animations have completed. This triggers layout on each animation + // frame; in the future we may want to do something smarter and more performant. + + final ViewGroup.LayoutParams lp = dismissView.getLayoutParams(); + final int originalHeight = dismissView.getHeight(); + + ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + --mDismissAnimationRefCount; + if (mDismissAnimationRefCount == 0) { + // No active animations, process all pending dismisses. + // Sort by descending position + Collections.sort(mPendingDismisses); + + int[] dismissPositions = new int[mPendingDismisses.size()]; + for (int i = mPendingDismisses.size() - 1; i >= 0; i--) { + dismissPositions[i] = mPendingDismisses.get(i).position; + } + mCallbacks.onDismiss(mListView, dismissPositions); + + // Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss + // animation with a stale position + mDownPosition = ListView.INVALID_POSITION; + + ViewGroup.LayoutParams lp; + for (PendingDismissData pendingDismiss : mPendingDismisses) { + // Reset view presentation + pendingDismiss.view.setAlpha(1f); + pendingDismiss.view.setTranslationX(0); + lp = pendingDismiss.view.getLayoutParams(); + lp.height = originalHeight; + pendingDismiss.view.setLayoutParams(lp); + } + + // Send a cancel event + long time = SystemClock.uptimeMillis(); + MotionEvent cancelEvent = MotionEvent.obtain(time, time, + MotionEvent.ACTION_CANCEL, 0, 0, 0); + mListView.dispatchTouchEvent(cancelEvent); + + mPendingDismisses.clear(); + } + } + }); + + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + lp.height = (Integer) valueAnimator.getAnimatedValue(); + dismissView.setLayoutParams(lp); + } + }); + + mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView)); + animator.start(); + } +} diff --git a/app/src/main/java/org/afhdownloader/SwipeDismissTouchListener.java b/app/src/main/java/org/afhdownloader/SwipeDismissTouchListener.java new file mode 100644 index 0000000..c1edfbc --- /dev/null +++ b/app/src/main/java/org/afhdownloader/SwipeDismissTouchListener.java @@ -0,0 +1,291 @@ +package org.afhdownloader; + +/* + * Copyright 2013 Google Inc. + * + * 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. + */ + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; +import android.app.ListActivity; +import android.app.ListFragment; +import android.os.Build; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListView; + +/** + * A {@link View.OnTouchListener} that makes any {@link View} dismissable when the + * user swipes (drags her finger) horizontally across the view. + * + *

For {@link ListView} list items that don't manage their own touch events + * (i.e. you're using + * {@link ListView#setOnItemClickListener(AdapterView.OnItemClickListener)} + * or an equivalent listener on {@link ListActivity} or + * {@link ListFragment}, use {@link SwipeDismissListViewTouchListener} instead.

+ * + *

Example usage:

+ * + *
+ * view.setOnTouchListener(new SwipeDismissTouchListener(
+ *         view,
+ *         null, // Optional token/cookie object
+ *         new SwipeDismissTouchListener.OnDismissCallback() {
+ *             public void onDismiss(View view, Object token) {
+ *                 parent.removeView(view);
+ *             }
+ *         }));
+ * 
+ * + *

This class Requires API level 12 or later due to use of {@link + * android.view.ViewPropertyAnimator}.

+ * + * @see SwipeDismissListViewTouchListener + */ +public class SwipeDismissTouchListener implements View.OnTouchListener { + // Cached ViewConfiguration and system-wide constant values + private int mSlop; + private int mMinFlingVelocity; + private int mMaxFlingVelocity; + private long mAnimationTime; + + // Fixed properties + private View mView; + private DismissCallbacks mCallbacks; + private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero + + // Transient properties + private float mDownX; + private float mDownY; + private boolean mSwiping; + private int mSwipingSlop; + private Object mToken; + private VelocityTracker mVelocityTracker; + private float mTranslationX; + + /** + * The callback interface used by {@link SwipeDismissTouchListener} to inform its client + * about a successful dismissal of the view for which it was created. + */ + public interface DismissCallbacks { + /** + * Called to determine whether the view can be dismissed. + */ + boolean canDismiss(Object token); + + /** + * Called when the user has indicated they she would like to dismiss the view. + * + * @param view The originating {@link View} to be dismissed. + * @param token The optional token passed to this object's constructor. + */ + void onDismiss(View view, Object token); + } + + /** + * Constructs a new swipe-to-dismiss touch listener for the given view. + * + * @param view The view to make dismissable. + * @param token An optional token/cookie object to be passed through to the callback. + * @param callbacks The callback to trigger when the user has indicated that she would like to + * dismiss this view. + */ + public SwipeDismissTouchListener(View view, Object token, DismissCallbacks callbacks) { + ViewConfiguration vc = ViewConfiguration.get(view.getContext()); + mSlop = vc.getScaledTouchSlop(); + mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16; + mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); + mAnimationTime = view.getContext().getResources().getInteger( + android.R.integer.config_shortAnimTime); + mView = view; + mToken = token; + mCallbacks = callbacks; + } + + @TargetApi(12) + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + // offset because the view is translated during swipe + motionEvent.offsetLocation(mTranslationX, 0); + + if (mViewWidth < 2) { + mViewWidth = mView.getWidth(); + } + + switch (motionEvent.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + // TODO: ensure this is a finger, and set a flag + mDownX = motionEvent.getRawX(); + mDownY = motionEvent.getRawY(); + if (mCallbacks.canDismiss(mToken)) { + mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker.addMovement(motionEvent); + } + return false; + } + + case MotionEvent.ACTION_UP: { + if (mVelocityTracker == null) { + break; + } + + float deltaX = motionEvent.getRawX() - mDownX; + mVelocityTracker.addMovement(motionEvent); + mVelocityTracker.computeCurrentVelocity(1000); + float velocityX = mVelocityTracker.getXVelocity(); + float absVelocityX = Math.abs(velocityX); + float absVelocityY = Math.abs(mVelocityTracker.getYVelocity()); + boolean dismiss = false; + boolean dismissRight = false; + if (Math.abs(deltaX) > mViewWidth / 2 && mSwiping) { + dismiss = true; + dismissRight = deltaX > 0; + } else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity + && absVelocityY < absVelocityX + && absVelocityY < absVelocityX && mSwiping) { + // dismiss only if flinging in the same direction as dragging + dismiss = (velocityX < 0) == (deltaX < 0); + dismissRight = mVelocityTracker.getXVelocity() > 0; + } + if (dismiss) { + // dismiss + if (Build.VERSION.SDK_INT >= 12) { + mView.animate() + .translationX(dismissRight ? mViewWidth : -mViewWidth) + .alpha(0) + .setDuration(mAnimationTime) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + performDismiss(); + } + }); + } else { + performDismiss(); + } + } else if (mSwiping) { + // cancel + if (Build.VERSION.SDK_INT >= 12) { + mView.animate() + .translationX(0) + .alpha(1) + .setDuration(mAnimationTime) + .setListener(null); + } + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + mTranslationX = 0; + mDownX = 0; + mDownY = 0; + mSwiping = false; + break; + } + + case MotionEvent.ACTION_CANCEL: { + if (mVelocityTracker == null) { + break; + } + if (Build.VERSION.SDK_INT >= 12) { + mView.animate() + .translationX(0) + .alpha(1) + .setDuration(mAnimationTime) + .setListener(null); + } + mVelocityTracker.recycle(); + mVelocityTracker = null; + mTranslationX = 0; + mDownX = 0; + mDownY = 0; + mSwiping = false; + break; + } + + case MotionEvent.ACTION_MOVE: { + if (mVelocityTracker == null) { + break; + } + + mVelocityTracker.addMovement(motionEvent); + float deltaX = motionEvent.getRawX() - mDownX; + float deltaY = motionEvent.getRawY() - mDownY; + if (Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) { + mSwiping = true; + mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop); + mView.getParent().requestDisallowInterceptTouchEvent(true); + + // Cancel listview's touch + MotionEvent cancelEvent = MotionEvent.obtain(motionEvent); + cancelEvent.setAction(MotionEvent.ACTION_CANCEL | + (motionEvent.getActionIndex() << + MotionEvent.ACTION_POINTER_INDEX_SHIFT)); + mView.onTouchEvent(cancelEvent); + cancelEvent.recycle(); + } + + if (mSwiping) { + mTranslationX = deltaX; + mView.setTranslationX(deltaX - mSwipingSlop); + // TODO: use an ease-out interpolator or such + mView.setAlpha(Math.max(0f, Math.min(1f, + 1f - 2f * Math.abs(deltaX) / mViewWidth))); + view.performClick(); + return true; + } + break; + } + } + return false; + } + + private void performDismiss() { + // Animate the dismissed view to zero-height and then fire the dismiss callback. + // This triggers layout on each animation frame; in the future we may want to do something + // smarter and more performant. + + final ViewGroup.LayoutParams lp = mView.getLayoutParams(); + final int originalHeight = mView.getHeight(); + + ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCallbacks.onDismiss(mView, mToken); + // Reset view presentation + mView.setAlpha(1f); + mView.setTranslationX(0); + lp.height = originalHeight; + mView.setLayoutParams(lp); + } + }); + + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + lp.height = (Integer) valueAnimator.getAnimatedValue(); + mView.setLayoutParams(lp); + } + }); + + animator.start(); + } +} \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index bfee718..7dddc43 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -8,5 +8,6 @@ 39788 3 0 - + .calc.md5 + .md5 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e547049..ca216db 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,5 +35,8 @@ HTTP Mirror POST Data MD5 indicator + Yes + No + Delete From f8ed27c3289dc2bbbc226950a576eb235f1d99a2 Mon Sep 17 00:00:00 2001 From: daktak Date: Tue, 12 Jul 2016 13:00:04 +1000 Subject: [PATCH 3/3] Heading exception clause --- app/src/main/java/org/afhdownloader/Download.java | 12 +++++++++--- app/src/main/res/values/donottranslate.xml | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/afhdownloader/Download.java b/app/src/main/java/org/afhdownloader/Download.java index 2e4c439..f9aa2bb 100644 --- a/app/src/main/java/org/afhdownloader/Download.java +++ b/app/src/main/java/org/afhdownloader/Download.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.ExecutionException; /** @@ -95,9 +96,14 @@ public String parseUrl(String url) { urls.add(link.attr("href")); } //set title - Elements h1s = doc.select("h1"); - String h1 = h1s.get(0).ownText(); - urls.add(h1); + String head = getString(R.string.app_name); + try { + Elements h1s = doc.select(getString(R.string.head_selector)); + head = h1s.get(0).ownText(); + } catch (Exception e) { + Log.d(LOGTAG,"Unable to find heading"); + } + urls.add(head); } catch (Throwable t) { Log.e(LOGTAG,t.getMessage()); diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 7dddc43..3039600 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -10,4 +10,5 @@ 0 .calc.md5 .md5 + h1