summaryrefslogtreecommitdiff
path: root/app/src/main/java/de/danoeh/antennapod/view
diff options
context:
space:
mode:
authorByteHamster <info@bytehamster.com>2020-02-21 19:02:53 +0100
committerByteHamster <info@bytehamster.com>2020-02-21 19:02:53 +0100
commitb3ea96e7b3d3409a02da64f9e93511cc3400709a (patch)
tree529b4b72831a8c9e2daac74839d71f70636e095c /app/src/main/java/de/danoeh/antennapod/view
parent7b5435082042dc77de6e3fb5f59bf55fc71d3aa8 (diff)
parent657d19ccc2c1e3de6555c5c220605bb59225e450 (diff)
downloadAntennaPod-b3ea96e7b3d3409a02da64f9e93511cc3400709a.zip
Merge branch 'develop' into speed-indicator-view
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod/view')
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java87
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java105
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/PieChartView.java76
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java167
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java19
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java44
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java41
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java213
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java15
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java62
10 files changed, 758 insertions, 71 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java
new file mode 100644
index 000000000..4b3c51cfc
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java
@@ -0,0 +1,87 @@
+package de.danoeh.antennapod.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import androidx.annotation.Nullable;
+
+public class CircularProgressBar extends View {
+ private static final float EPSILON = 0.005f;
+
+ private final Paint paintBackground = new Paint();
+ private final Paint paintProgress = new Paint();
+ private float percentage = 0;
+ private float targetPercentage = 0;
+ private Object tag = null;
+
+ public CircularProgressBar(Context context) {
+ super(context);
+ setup();
+ }
+
+ public CircularProgressBar(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ setup();
+ }
+
+ public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setup();
+ }
+
+ private void setup() {
+ paintBackground.setAntiAlias(true);
+ paintBackground.setStyle(Paint.Style.STROKE);
+
+ paintProgress.setAntiAlias(true);
+ paintProgress.setStyle(Paint.Style.STROKE);
+ paintProgress.setStrokeCap(Paint.Cap.ROUND);
+
+ int[] colorAttrs = new int[] { android.R.attr.textColorPrimary, android.R.attr.textColorSecondary };
+ TypedArray a = getContext().obtainStyledAttributes(colorAttrs);
+ paintProgress.setColor(a.getColor(0, 0xffffffff));
+ paintBackground.setColor(a.getColor(1, 0xffffffff));
+ a.recycle();
+ }
+
+ /**
+ * Sets the percentage to be displayed.
+ * @param percentage Number from 0 to 1
+ * @param tag When the tag is the same as last time calling setPercentage, the update is animated
+ */
+ public void setPercentage(float percentage, Object tag) {
+ targetPercentage = percentage;
+
+ if (tag == null || !tag.equals(this.tag)) {
+ // Do not animate
+ this.percentage = percentage;
+ this.tag = tag;
+ }
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ float padding = getHeight() * 0.06f;
+ paintBackground.setStrokeWidth(getHeight() * 0.02f);
+ paintProgress.setStrokeWidth(padding);
+ RectF bounds = new RectF(padding, padding, getWidth() - padding, getHeight() - padding);
+ canvas.drawArc(bounds, 0, 360, false, paintBackground);
+
+ if (percentage > EPSILON && 1 - percentage > EPSILON) {
+ canvas.drawArc(bounds, -90, percentage * 360, false, paintProgress);
+ }
+
+ if (Math.abs(percentage - targetPercentage) > EPSILON) {
+ float delta = Math.min(0.02f, Math.abs(targetPercentage - percentage));
+ percentage += delta * ((targetPercentage - percentage) > 0 ? 1f : -1f);
+ invalidate();
+ }
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java b/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java
new file mode 100644
index 000000000..60ef820a9
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java
@@ -0,0 +1,105 @@
+package de.danoeh.antennapod.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+import androidx.annotation.Nullable;
+import androidx.vectordrawable.graphics.drawable.ArgbEvaluator;
+import androidx.viewpager.widget.ViewPager;
+
+public class PagerIndicatorView extends View {
+ private final Paint paint = new Paint();
+ private float position = 0;
+ private int numPages = 0;
+ private int disabledPage = -1;
+ private int circleColor = 0;
+ private int circleColorHighlight = -1;
+
+ public PagerIndicatorView(Context context) {
+ super(context);
+ setup();
+ }
+
+ public PagerIndicatorView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ setup();
+ }
+
+ public PagerIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setup();
+ }
+
+ private void setup() {
+ paint.setAntiAlias(true);
+ paint.setStyle(Paint.Style.FILL);
+
+ int[] colorAttrs = new int[] { android.R.attr.textColorSecondary };
+ TypedArray a = getContext().obtainStyledAttributes(colorAttrs);
+ circleColorHighlight = a.getColor(0, 0xffffffff);
+ circleColor = (Integer) new ArgbEvaluator().evaluate(0.8f, 0x00ffffff, circleColorHighlight);
+ a.recycle();
+ }
+
+ public void setViewPager(ViewPager pager) {
+ numPages = pager.getAdapter().getCount();
+ pager.getAdapter().registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ numPages = pager.getAdapter().getCount();
+ invalidate();
+ }
+ });
+ pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ PagerIndicatorView.this.position = position + positionOffset;
+ invalidate();
+ }
+ });
+ }
+
+ public void setDisabledPage(int disabledPage) {
+ this.disabledPage = disabledPage;
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ for (int i = 0; i < numPages; i++) {
+ if ((int) Math.floor(position) == i) {
+ // This is the current dot
+ drawCircle(canvas, i, (float) (1 - (position - Math.floor(position))));
+ } else if ((int) Math.ceil(position) == i) {
+ // This is the next dot
+ drawCircle(canvas, i, (float) (position - Math.floor(position)));
+ } else {
+ drawCircle(canvas, i, 0);
+ }
+ }
+ }
+
+ private void drawCircle(Canvas canvas, int position, float frac) {
+ float circleRadiusSmall = canvas.getHeight() * 0.26f;
+ float circleRadiusBig = canvas.getHeight() * 0.35f;
+ float circleRadiusDelta = (circleRadiusBig - circleRadiusSmall);
+ float start = 0.5f * (canvas.getWidth() - numPages * 1.5f * canvas.getHeight());
+ paint.setStrokeWidth(canvas.getHeight() * 0.3f);
+
+ if (position == disabledPage) {
+ paint.setStyle(Paint.Style.STROKE);
+ } else {
+ paint.setStyle(Paint.Style.FILL_AND_STROKE);
+ }
+
+ paint.setColor((Integer) new ArgbEvaluator().evaluate(frac, circleColor, circleColorHighlight));
+ canvas.drawCircle(start + (position * 1.5f + 0.75f) * canvas.getHeight(), 0.5f * canvas.getHeight(),
+ circleRadiusSmall + frac * circleRadiusDelta, paint);
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java b/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java
index d1b2abf23..ab4920119 100644
--- a/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java
+++ b/app/src/main/java/de/danoeh/antennapod/view/PieChartView.java
@@ -39,14 +39,10 @@ public class PieChartView extends AppCompatImageView {
}
/**
- * Set array od names, array of values and array of colors.
+ * Set of data values to display.
*/
- public void setData(float[] dataValues) {
- drawable.dataValues = dataValues;
- drawable.valueSum = 0;
- for (float datum : dataValues) {
- drawable.valueSum += datum;
- }
+ public void setData(PieChartData data) {
+ drawable.data = data;
}
@Override
@@ -56,15 +52,49 @@ public class PieChartView extends AppCompatImageView {
setMeasuredDimension(width, width / 2);
}
- private static class PieChartDrawable extends Drawable {
- private static final float MIN_DEGREES = 10f;
- private static final float PADDING_DEGREES = 3f;
- private static final float STROKE_SIZE = 15f;
+ public static class PieChartData {
private static final int[] COLOR_VALUES = new int[]{0xFF3775E6, 0xffe51c23, 0xffff9800, 0xff259b24, 0xff9c27b0,
0xff0099c6, 0xffdd4477, 0xff66aa00, 0xffb82e2e, 0xff316395,
0xff994499, 0xff22aa99, 0xffaaaa11, 0xff6633cc, 0xff0073e6};
- private float[] dataValues;
- private float valueSum;
+
+ private final float valueSum;
+ private final float[] values;
+
+ public PieChartData(float[] values) {
+ this.values = values;
+ float valueSum = 0;
+ for (float datum : values) {
+ valueSum += datum;
+ }
+ this.valueSum = valueSum;
+ }
+
+ public float getSum() {
+ return valueSum;
+ }
+
+ public float getPercentageOfItem(int index) {
+ if (valueSum == 0) {
+ return 0;
+ }
+ return values[index] / valueSum;
+ }
+
+ public boolean isLargeEnoughToDisplay(int index) {
+ return getPercentageOfItem(index) > 0.04;
+ }
+
+ public int getColorOfItem(int index) {
+ if (!isLargeEnoughToDisplay(index)) {
+ return Color.GRAY;
+ }
+ return COLOR_VALUES[index % COLOR_VALUES.length];
+ }
+ }
+
+ private static class PieChartDrawable extends Drawable {
+ private static final float PADDING_DEGREES = 3f;
+ private PieChartData data;
private final Paint paint;
private PieChartDrawable() {
@@ -73,27 +103,25 @@ public class PieChartView extends AppCompatImageView {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
- paint.setStrokeWidth(STROKE_SIZE);
}
@Override
public void draw(@NonNull Canvas canvas) {
- if (valueSum == 0) {
- return;
- }
- float radius = getBounds().height() - STROKE_SIZE;
+ final float strokeSize = getBounds().height() / 30f;
+ paint.setStrokeWidth(strokeSize);
+
+ float radius = getBounds().height() - strokeSize;
float center = getBounds().width() / 2.f;
- RectF arcBounds = new RectF(center - radius, STROKE_SIZE, center + radius, STROKE_SIZE + radius * 2);
+ RectF arcBounds = new RectF(center - radius, strokeSize, center + radius, strokeSize + radius * 2);
float startAngle = 180;
- for (int i = 0; i < dataValues.length; i++) {
- float datum = dataValues[i];
- float sweepAngle = (180f - PADDING_DEGREES) * (datum / valueSum);
- if (sweepAngle < MIN_DEGREES) {
+ for (int i = 0; i < data.values.length; i++) {
+ if (!data.isLargeEnoughToDisplay(i)) {
break;
}
- paint.setColor(COLOR_VALUES[i % COLOR_VALUES.length]);
+ paint.setColor(data.getColorOfItem(i));
float padding = i == 0 ? PADDING_DEGREES / 2 : PADDING_DEGREES;
+ float sweepAngle = (180f - PADDING_DEGREES) * data.getPercentageOfItem(i);
canvas.drawArc(arcBounds, startAngle + padding, sweepAngle - padding, false, paint);
startAngle = startAngle + sweepAngle;
}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java b/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java
new file mode 100644
index 000000000..3ea57eb5e
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/view/ShownotesWebView.java
@@ -0,0 +1,167 @@
+package de.danoeh.antennapod.view;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import androidx.core.content.ContextCompat;
+import com.google.android.material.snackbar.Snackbar;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.util.Consumer;
+import de.danoeh.antennapod.core.util.Converter;
+import de.danoeh.antennapod.core.util.IntentUtils;
+import de.danoeh.antennapod.core.util.NetworkUtils;
+import de.danoeh.antennapod.core.util.ShareUtils;
+import de.danoeh.antennapod.core.util.playback.Timeline;
+
+public class ShownotesWebView extends WebView implements View.OnLongClickListener {
+ private static final String TAG = "ShownotesWebView";
+
+ /**
+ * URL that was selected via long-press.
+ */
+ private String selectedUrl;
+ private Consumer<Integer> timecodeSelectedListener;
+ private Runnable pageFinishedListener;
+
+ public ShownotesWebView(Context context) {
+ super(context);
+ setup();
+ }
+
+ public ShownotesWebView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setup();
+ }
+
+ public ShownotesWebView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ setup();
+ }
+
+ private void setup() {
+ setBackgroundColor(Color.TRANSPARENT);
+ if (!NetworkUtils.networkAvailable()) {
+ getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
+ // Use cached resources, even if they have expired
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
+ }
+ getSettings().setUseWideViewPort(false);
+ getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
+ getSettings().setLoadWithOverviewMode(true);
+ setOnLongClickListener(this);
+
+ setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ if (Timeline.isTimecodeLink(url) && timecodeSelectedListener != null) {
+ timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(selectedUrl));
+ } else {
+ IntentUtils.openInBrowser(getContext(), url);
+ }
+ return true;
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ Log.d(TAG, "Page finished");
+ if (pageFinishedListener != null) {
+ pageFinishedListener.run();
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ WebView.HitTestResult r = getHitTestResult();
+ if (r != null && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
+ Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra());
+ selectedUrl = r.getExtra();
+ showContextMenu();
+ return true;
+ }
+ selectedUrl = null;
+ return false;
+ }
+
+ public boolean onContextItemSelected(MenuItem item) {
+ if (selectedUrl == null) {
+ return false;
+ }
+
+ switch (item.getItemId()) {
+ case R.id.open_in_browser_item:
+ IntentUtils.openInBrowser(getContext(), selectedUrl);
+ break;
+ case R.id.share_url_item:
+ ShareUtils.shareLink(getContext(), selectedUrl);
+ break;
+ case R.id.copy_url_item:
+ ClipData clipData = ClipData.newPlainText(selectedUrl, selectedUrl);
+ android.content.ClipboardManager cm = (android.content.ClipboardManager) getContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(clipData);
+ Snackbar.make(this, R.string.copied_url_msg, Snackbar.LENGTH_LONG).show();
+ break;
+ case R.id.go_to_position_item:
+ if (Timeline.isTimecodeLink(selectedUrl) && timecodeSelectedListener != null) {
+ timecodeSelectedListener.accept(Timeline.getTimecodeLinkTime(selectedUrl));
+ } else {
+ Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: " + selectedUrl);
+ }
+ break;
+ default:
+ selectedUrl = null;
+ return false;
+
+ }
+ selectedUrl = null;
+ return true;
+ }
+
+ @Override
+ protected void onCreateContextMenu(ContextMenu menu) {
+ super.onCreateContextMenu(menu);
+ if (selectedUrl == null) {
+ return;
+ }
+
+ if (Timeline.isTimecodeLink(selectedUrl)) {
+ menu.add(Menu.NONE, R.id.go_to_position_item, Menu.NONE, R.string.go_to_position_label);
+ menu.setHeaderTitle(Converter.getDurationStringLong(Timeline.getTimecodeLinkTime(selectedUrl)));
+ } else {
+ Uri uri = Uri.parse(selectedUrl);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ if (IntentUtils.isCallable(getContext(), intent)) {
+ menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, R.string.open_in_browser_label);
+ }
+ menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, R.string.copy_url_label);
+ menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, R.string.share_url_label);
+ menu.setHeaderTitle(selectedUrl);
+ }
+ }
+
+ public void setTimecodeSelectedListener(Consumer<Integer> timecodeSelectedListener) {
+ this.timecodeSelectedListener = timecodeSelectedListener;
+ }
+
+ public void setPageFinishedListener(Runnable pageFinishedListener) {
+ this.pageFinishedListener = pageFinishedListener;
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java
index f82309c4a..dcf1edbe7 100644
--- a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java
+++ b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java
@@ -1,13 +1,16 @@
package de.danoeh.antennapod.view;
import android.content.Context;
+import android.content.res.TypedArray;
import androidx.appcompat.widget.AppCompatImageView;
import android.util.AttributeSet;
+import de.danoeh.antennapod.core.R;
/**
* From http://stackoverflow.com/a/19449488/6839
*/
public class SquareImageView extends AppCompatImageView {
+ private boolean useMinimum = false;
public SquareImageView(Context context) {
super(context);
@@ -15,19 +18,29 @@ public class SquareImageView extends AppCompatImageView {
public SquareImageView(Context context, AttributeSet attrs) {
super(context, attrs);
+ loadAttrs(context, attrs);
}
public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ loadAttrs(context, attrs);
+ }
+
+ private void loadAttrs(Context context, AttributeSet attrs) {
+ TypedArray a = context.obtainStyledAttributes(attrs, new int[]{R.styleable.SquareImageView_useMinimum});
+ useMinimum = a.getBoolean(0, false);
+ a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width = getMeasuredWidth();
- //noinspection SuspiciousNameCombination
- setMeasuredDimension(width, width);
+ int size = getMeasuredWidth();
+ if (useMinimum) {
+ size = Math.min(getMeasuredWidth(), getMeasuredHeight());
+ }
+ setMeasuredDimension(size, size);
}
} \ No newline at end of file
diff --git a/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java b/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java
deleted file mode 100644
index f4ee092df..000000000
--- a/app/src/main/java/de/danoeh/antennapod/view/SwipeGestureDetector.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package de.danoeh.antennapod.view;
-
-import android.util.Log;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-
-public class SwipeGestureDetector extends GestureDetector.SimpleOnGestureListener {
-
- private static final String TAG = "SwipeGestureDetector";
-
- private static final int SWIPE_MIN_DISTANCE = 120;
- private static final int SWIPE_MAX_OFF_PATH = 250;
- private static final int SWIPE_THRESHOLD_VELOCITY = 200;
-
- private final OnSwipeGesture callback;
-
- public SwipeGestureDetector(OnSwipeGesture callback) {
- this.callback = callback;
- }
-
- @Override
- public boolean onDown(MotionEvent e) {
- return true;
- }
-
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- try {
- if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
- return false;
- if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
- && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
- return callback.onSwipeRightToLeft();
- } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
- && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
- return callback.onSwipeLeftToRight();
- }
- } catch (Exception e) {
- Log.d(TAG, Log.getStackTraceString(e));
- }
- return false;
- }
-
-}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java
new file mode 100644
index 000000000..d48db196f
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/DownloadItemViewHolder.java
@@ -0,0 +1,41 @@
+package de.danoeh.antennapod.view.viewholder;
+
+import android.content.Context;
+import android.os.Build;
+import android.text.Layout;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.recyclerview.widget.RecyclerView;
+import com.joanzapata.iconify.widget.IconTextView;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.view.CircularProgressBar;
+
+public class DownloadItemViewHolder extends RecyclerView.ViewHolder {
+ public final View secondaryActionButton;
+ public final ImageView secondaryActionIcon;
+ public final CircularProgressBar secondaryActionProgress;
+ public final IconTextView icon;
+ public final TextView title;
+ public final TextView type;
+ public final TextView date;
+ public final TextView reason;
+
+ public DownloadItemViewHolder(Context context, ViewGroup parent) {
+ super(LayoutInflater.from(context).inflate(R.layout.downloadlog_item, parent, false));
+ date = itemView.findViewById(R.id.txtvDate);
+ type = itemView.findViewById(R.id.txtvType);
+ icon = itemView.findViewById(R.id.txtvIcon);
+ reason = itemView.findViewById(R.id.txtvReason);
+ secondaryActionProgress = itemView.findViewById(R.id.secondaryActionProgress);
+ secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton);
+ secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon);
+ title = itemView.findViewById(R.id.txtvTitle);
+ if (Build.VERSION.SDK_INT >= 23) {
+ title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+ }
+ itemView.setTag(this);
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java
new file mode 100644
index 000000000..369574190
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java
@@ -0,0 +1,213 @@
+package de.danoeh.antennapod.view.viewholder;
+
+import android.graphics.Color;
+import android.os.Build;
+import android.text.Layout;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import androidx.cardview.widget.CardView;
+import androidx.recyclerview.widget.RecyclerView;
+import com.joanzapata.iconify.Iconify;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.adapter.CoverLoader;
+import de.danoeh.antennapod.adapter.QueueRecyclerAdapter;
+import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton;
+import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
+import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.core.util.Converter;
+import de.danoeh.antennapod.core.util.DateUtils;
+import de.danoeh.antennapod.core.util.NetworkUtils;
+import de.danoeh.antennapod.core.util.ThemeUtils;
+import de.danoeh.antennapod.view.CircularProgressBar;
+
+/**
+ * Holds the view which shows FeedItems.
+ */
+public class EpisodeItemViewHolder extends FeedComponentViewHolder
+ implements QueueRecyclerAdapter.ItemTouchHelperViewHolder {
+ private static final String TAG = "EpisodeItemViewHolder";
+
+ private final View container;
+ public final ImageView dragHandle;
+ private final TextView placeholder;
+ private final ImageView cover;
+ private final TextView title;
+ private final TextView pubDate;
+ private final TextView position;
+ private final TextView duration;
+ private final TextView size;
+ public final TextView isNew;
+ public final ImageView isInQueue;
+ private final ImageView isVideo;
+ public final ImageView isFavorite;
+ private final ProgressBar progressBar;
+ public final View secondaryActionButton;
+ public final ImageView secondaryActionIcon;
+ private final CircularProgressBar secondaryActionProgress;
+ private final TextView separatorIcons;
+ public final CardView coverHolder;
+
+ private final MainActivity activity;
+ private FeedItem item;
+
+ public EpisodeItemViewHolder(MainActivity activity, ViewGroup parent) {
+ super(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false));
+ this.activity = activity;
+ container = itemView.findViewById(R.id.container);
+ dragHandle = itemView.findViewById(R.id.drag_handle);
+ placeholder = itemView.findViewById(R.id.txtvPlaceholder);
+ cover = itemView.findViewById(R.id.imgvCover);
+ title = itemView.findViewById(R.id.txtvTitle);
+ if (Build.VERSION.SDK_INT >= 23) {
+ title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+ }
+ pubDate = itemView.findViewById(R.id.txtvPubDate);
+ position = itemView.findViewById(R.id.txtvPosition);
+ duration = itemView.findViewById(R.id.txtvDuration);
+ progressBar = itemView.findViewById(R.id.progressBar);
+ isInQueue = itemView.findViewById(R.id.ivInPlaylist);
+ isVideo = itemView.findViewById(R.id.ivIsVideo);
+ isNew = itemView.findViewById(R.id.statusUnread);
+ isFavorite = itemView.findViewById(R.id.isFavorite);
+ size = itemView.findViewById(R.id.size);
+ separatorIcons = itemView.findViewById(R.id.separatorIcons);
+ secondaryActionProgress = itemView.findViewById(R.id.secondaryActionProgress);
+ secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton);
+ secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon);
+ coverHolder = itemView.findViewById(R.id.coverHolder);
+ itemView.setTag(this);
+ }
+
+ @Override
+ public void onItemSelected() {
+ itemView.setAlpha(0.5f);
+ }
+
+ @Override
+ public void onItemClear() {
+ itemView.setAlpha(1.0f);
+ }
+
+ public void bind(FeedItem item) {
+ this.item = item;
+ placeholder.setText(item.getFeed().getTitle());
+ title.setText(item.getTitle());
+ pubDate.setText(DateUtils.formatAbbrev(activity, item.getPubDate()));
+ isNew.setVisibility(item.isNew() ? View.VISIBLE : View.GONE);
+ isFavorite.setVisibility(item.isTagged(FeedItem.TAG_FAVORITE) ? View.VISIBLE : View.GONE);
+ isInQueue.setVisibility(item.isTagged(FeedItem.TAG_QUEUE) ? View.VISIBLE : View.GONE);
+ itemView.setAlpha(item.isPlayed() ? 0.5f : 1.0f);
+
+ ItemActionButton actionButton = ItemActionButton.forItem(item, true, true);
+ actionButton.configure(secondaryActionButton, secondaryActionIcon, activity);
+ secondaryActionButton.setFocusable(false);
+
+ if (item.getMedia() != null) {
+ bind(item.getMedia());
+ } else {
+ secondaryActionProgress.setPercentage(0, item);
+ }
+
+ if (coverHolder.getVisibility() == View.VISIBLE) {
+ new CoverLoader(activity)
+ .withUri(ImageResourceUtils.getImageLocation(item))
+ .withFallbackUri(item.getFeed().getImageLocation())
+ .withPlaceholderView(placeholder)
+ .withCoverView(cover)
+ .load();
+ }
+ }
+
+ private void bind(FeedMedia media) {
+ isVideo.setVisibility(media.getMediaType() == MediaType.VIDEO ? View.VISIBLE : View.GONE);
+ duration.setText(Converter.getDurationStringLong(media.getDuration()));
+
+ if (media.isCurrentlyPlaying()) {
+ container.setBackgroundColor(ThemeUtils.getColorFromAttr(activity, R.attr.currently_playing_background));
+ } else {
+ container.setBackgroundResource(ThemeUtils.getDrawableFromAttr(activity, R.attr.selectableItemBackground));
+ }
+
+ if (DownloadRequester.getInstance().isDownloadingFile(media)) {
+ final DownloadRequest downloadRequest = DownloadRequester.getInstance().getRequestFor(media);
+ float percent = 0.01f * downloadRequest.getProgressPercent();
+ secondaryActionProgress.setPercentage(Math.max(percent, 0.01f), item);
+ } else if (media.isDownloaded()) {
+ secondaryActionProgress.setPercentage(1, item); // Do not animate 100% -> 0%
+ } else {
+ secondaryActionProgress.setPercentage(0, item); // Animate X% -> 0%
+ }
+
+ if (media.getDuration() > 0
+ && (item.getState() == FeedItem.State.PLAYING || item.getState() == FeedItem.State.IN_PROGRESS)) {
+ int progress = (int) (100.0 * media.getPosition() / media.getDuration());
+ progressBar.setProgress(progress);
+ position.setText(Converter.getDurationStringLong(media.getPosition()));
+ duration.setText(Converter.getDurationStringLong(media.getDuration()));
+ progressBar.setVisibility(View.VISIBLE);
+ position.setVisibility(View.VISIBLE);
+ } else {
+ progressBar.setVisibility(View.GONE);
+ position.setVisibility(View.GONE);
+ }
+
+ if (media.getSize() > 0) {
+ size.setText(Converter.byteToString(media.getSize()));
+ } else if (NetworkUtils.isEpisodeHeadDownloadAllowed() && !media.checkedOnSizeButUnknown()) {
+ size.setText("{fa-spinner}");
+ Iconify.addIcons(size);
+ NetworkUtils.getFeedMediaSizeObservable(media).subscribe(
+ sizeValue -> {
+ if (sizeValue > 0) {
+ size.setText(Converter.byteToString(sizeValue));
+ } else {
+ size.setText("");
+ }
+ }, error -> {
+ size.setText("");
+ Log.e(TAG, Log.getStackTraceString(error));
+ });
+ } else {
+ size.setText("");
+ }
+ }
+
+ public FeedItem getFeedItem() {
+ return item;
+ }
+
+ public boolean isCurrentlyPlayingItem() {
+ return item.getMedia() != null && item.getMedia().isCurrentlyPlaying();
+ }
+
+ public void notifyPlaybackPositionUpdated(PlaybackPositionEvent event) {
+ progressBar.setProgress((int) (100.0 * event.getPosition() / event.getDuration()));
+ position.setText(Converter.getDurationStringLong(event.getPosition()));
+ duration.setText(Converter.getDurationStringLong(event.getDuration()));
+ }
+
+ /**
+ * Hides the separator dot between icons and text if there are no icons.
+ */
+ public void hideSeparatorIfNecessary() {
+ boolean hasIcons = isNew.getVisibility() == View.VISIBLE
+ || isInQueue.getVisibility() == View.VISIBLE
+ || isVideo.getVisibility() == View.VISIBLE
+ || isFavorite.getVisibility() == View.VISIBLE
+ || isNew.getVisibility() == View.VISIBLE;
+ separatorIcons.setVisibility(hasIcons ? View.VISIBLE : View.GONE);
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java
new file mode 100644
index 000000000..f55ea9bc8
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedComponentViewHolder.java
@@ -0,0 +1,15 @@
+package de.danoeh.antennapod.view.viewholder;
+
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+/**
+ * Holds the view which shows FeedComponents.
+ */
+public class FeedComponentViewHolder extends RecyclerView.ViewHolder {
+
+ public FeedComponentViewHolder(@NonNull View itemView) {
+ super(itemView);
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java
new file mode 100644
index 000000000..83250bbfa
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/FeedViewHolder.java
@@ -0,0 +1,62 @@
+package de.danoeh.antennapod.view.viewholder;
+
+import android.os.Build;
+import android.text.Layout;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.cardview.widget.CardView;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.adapter.CoverLoader;
+import de.danoeh.antennapod.core.feed.Feed;
+
+/**
+ * Holds the view which shows feeds.
+ */
+public class FeedViewHolder extends FeedComponentViewHolder {
+ private static final String TAG = "FeedViewHolder";
+
+ private final TextView placeholder;
+ private final ImageView cover;
+ private final TextView title;
+ public final CardView coverHolder;
+
+ private final MainActivity activity;
+ private Feed feed;
+
+ public FeedViewHolder(MainActivity activity, ViewGroup parent) {
+ super(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false));
+ this.activity = activity;
+ placeholder = itemView.findViewById(R.id.txtvPlaceholder);
+ cover = itemView.findViewById(R.id.imgvCover);
+ coverHolder = itemView.findViewById(R.id.coverHolder);
+ title = itemView.findViewById(R.id.txtvTitle);
+ if (Build.VERSION.SDK_INT >= 23) {
+ title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+ }
+
+ itemView.findViewById(R.id.secondaryActionButton).setVisibility(View.GONE);
+ itemView.findViewById(R.id.status).setVisibility(View.GONE);
+ itemView.findViewById(R.id.progress).setVisibility(View.GONE);
+ itemView.findViewById(R.id.drag_handle).setVisibility(View.GONE);
+ itemView.setTag(this);
+ }
+
+ public void bind(Feed feed) {
+ this.feed = feed;
+ placeholder.setText(feed.getTitle());
+ title.setText(feed.getTitle());
+
+ if (coverHolder.getVisibility() == View.VISIBLE) {
+ new CoverLoader(activity)
+ .withUri(feed.getImageLocation())
+ .withPlaceholderView(placeholder)
+ .withCoverView(cover)
+ .load();
+ }
+ }
+
+}