summaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java44
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java48
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java23
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/ChapterSeekBar.java129
-rw-r--r--app/src/main/res/layout/audioplayer_fragment.xml2
6 files changed, 215 insertions, 34 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
index bd2c7f9a6..045cc5f21 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
@@ -16,6 +16,7 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@@ -51,7 +52,6 @@ import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeExceptio
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.FileNameGenerator;
import de.danoeh.antennapod.core.util.IntentUtils;
-import de.danoeh.antennapod.core.util.Optional;
import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.core.util.URLChecker;
import de.danoeh.antennapod.core.util.playback.RemoteMedia;
@@ -60,9 +60,11 @@ import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText;
import de.danoeh.antennapod.databinding.OnlinefeedviewActivityBinding;
import de.danoeh.antennapod.dialog.AuthenticationDialog;
import de.danoeh.antennapod.discovery.PodcastSearcherRegistry;
+import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
+import io.reactivex.observers.DisposableMaybeObserver;
import io.reactivex.schedulers.Schedulers;
import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus;
@@ -321,34 +323,48 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
}
Log.d(TAG, "Parsing feed");
- parser = Observable.fromCallable(this::doParseFeed)
+ parser = Maybe.fromCallable(this::doParseFeed)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(optionalResult -> {
- if(optionalResult.isPresent()) {
- FeedHandlerResult result = optionalResult.get();
+ .subscribeWith(new DisposableMaybeObserver<FeedHandlerResult>() {
+ @Override
+ public void onSuccess(@NonNull FeedHandlerResult result) {
beforeShowFeedInformation(result.feed);
showFeedInformation(result.feed, result.alternateFeedUrls);
}
- }, error -> {
- String errorMsg = DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
- OnlineFeedViewActivity.this) + " (" + error.getMessage() + ")";
- showErrorDialog(errorMsg);
- Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error));
+
+ @Override
+ public void onComplete() {
+ // Ignore null result: We showed the discovery dialog.
+ }
+
+ @Override
+ public void onError(@NonNull Throwable error) {
+ String errorMsg = DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
+ OnlineFeedViewActivity.this) + " (" + error.getMessage() + ")";
+ showErrorDialog(errorMsg);
+ Log.d(TAG, "Feed parser exception: " + Log.getStackTraceString(error));
+ }
});
}
- @NonNull
- private Optional<FeedHandlerResult> doParseFeed() throws Exception {
+ /**
+ * Try to parse the feed.
+ * @return The FeedHandlerResult if successful.
+ * Null if unsuccessful but we started another attempt.
+ * @throws Exception If unsuccessful but we do not know a resolution.
+ */
+ @Nullable
+ private FeedHandlerResult doParseFeed() throws Exception {
FeedHandler handler = new FeedHandler();
try {
- return Optional.of(handler.parseFeed(feed));
+ return handler.parseFeed(feed);
} catch (UnsupportedFeedtypeException e) {
Log.d(TAG, "Unsupported feed type detected");
if ("html".equalsIgnoreCase(e.getRootElement())) {
boolean dialogShown = showFeedDiscoveryDialog(new File(feed.getFile_url()), feed.getDownload_url());
if (dialogShown) {
- return Optional.empty();
+ return null; // Should not display an error message
} else {
Log.d(TAG, "Supplied feed is an HTML web page that has no references to any feed");
throw e;
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java
index bdaae1bea..51f264e56 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java
@@ -29,12 +29,14 @@ import de.danoeh.antennapod.activity.CastEnabledActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.event.FavoritesEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
+import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
+import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
@@ -46,6 +48,7 @@ import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
import de.danoeh.antennapod.dialog.SleepTimerDialog;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
+import de.danoeh.antennapod.view.ChapterSeekBar;
import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView;
import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers;
@@ -63,7 +66,7 @@ import java.util.List;
* Shows the audio player.
*/
public class AudioPlayerFragment extends Fragment implements
- SeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
+ ChapterSeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
public static final String TAG = "AudioPlayerFragment";
private static final int POS_COVER = 0;
private static final int POS_DESCR = 1;
@@ -77,7 +80,7 @@ public class AudioPlayerFragment extends Fragment implements
private ViewPager2 pager;
private TextView txtvPosition;
private TextView txtvLength;
- private SeekBar sbPosition;
+ private ChapterSeekBar sbPosition;
private ImageButton butRev;
private TextView txtvRev;
private ImageButton butPlay;
@@ -172,12 +175,33 @@ public class AudioPlayerFragment extends Fragment implements
return root;
}
- public void setHasChapters(boolean hasChapters) {
+ private void setHasChapters(boolean hasChapters) {
this.hasChapters = hasChapters;
tabLayoutMediator.detach();
tabLayoutMediator.attach();
}
+ private void setChapterDividers(Playable media) {
+
+ if (media == null) {
+ return;
+ }
+
+ float[] dividerPos = null;
+
+ if (hasChapters) {
+ List<Chapter> chapters = media.getChapters();
+ dividerPos = new float[chapters.size()];
+ float duration = media.getDuration();
+
+ for (int i = 0; i < chapters.size(); i++) {
+ dividerPos[i] = chapters.get(i).getStart() / duration;
+ }
+ }
+
+ sbPosition.setDividerPos(dividerPos);
+ }
+
public View getExternalPlayerHolder() {
return getView().findViewById(R.id.playerFragment);
}
@@ -298,16 +322,17 @@ public class AudioPlayerFragment extends Fragment implements
disposable = Maybe.create(emitter -> {
Playable media = controller.getMedia();
if (media != null) {
+ ChapterUtils.loadChapters(media, getContext());
emitter.onSuccess(media);
} else {
emitter.onComplete();
}
})
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(media -> updateUi((Playable) media),
- error -> Log.e(TAG, Log.getStackTraceString(error)),
- () -> updateUi(null));
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(media -> updateUi((Playable) media),
+ error -> Log.e(TAG, Log.getStackTraceString(error)),
+ () -> updateUi(null));
}
private PlaybackController newPlaybackController() {
@@ -389,8 +414,15 @@ public class AudioPlayerFragment extends Fragment implements
if (controller == null) {
return;
}
+
+ if (media != null && media.getChapters() != null) {
+ setHasChapters(media.getChapters().size() > 0);
+ } else {
+ setHasChapters(false);
+ }
updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration()));
updatePlaybackSpeedButton(media);
+ setChapterDividers(media);
setupOptionsMenu(media);
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java
index b578a603f..acda462bd 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java
@@ -118,7 +118,7 @@ public class ChaptersFragment extends Fragment {
disposable = Maybe.create(emitter -> {
Playable media = controller.getMedia();
if (media != null) {
- media.loadChapterMarks(getContext());
+ ChapterUtils.loadChapters(media, getContext());
emitter.onSuccess(media);
} else {
emitter.onComplete();
@@ -137,7 +137,6 @@ public class ChaptersFragment extends Fragment {
return;
}
adapter.setMedia(media);
- ((AudioPlayerFragment) getParentFragment()).setHasChapters(adapter.getItemCount() > 0);
int positionOfCurrentChapter = getCurrentChapter(media);
updateChapterSelection(positionOfCurrentChapter);
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
index c86fdc070..6be532887 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
@@ -56,7 +56,6 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.FeedItemUtil;
-import de.danoeh.antennapod.core.util.Optional;
import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
@@ -549,15 +548,21 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
disposable = Observable.fromCallable(this::loadData)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(result -> {
- feed = result.orElse(null);
- refreshHeaderView();
- displayList();
- }, error -> Log.e(TAG, Log.getStackTraceString(error)));
+ .subscribe(
+ result -> {
+ feed = result;
+ refreshHeaderView();
+ displayList();
+ }, error -> {
+ feed = null;
+ refreshHeaderView();
+ displayList();
+ Log.e(TAG, Log.getStackTraceString(error));
+ });
}
- @NonNull
- private Optional<Feed> loadData() {
+ @Nullable
+ private Feed loadData() {
Feed feed = DBReader.getFeed(feedID);
if (feed != null && feed.getItemFilter() != null) {
DBReader.loadAdditionalFeedItemListData(feed.getItems());
@@ -569,7 +574,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
FeedItemPermutors.getPermutor(feed.getSortOrder()).reorder(feedItems);
feed.setItems(feedItems);
}
- return Optional.ofNullable(feed);
+ return feed;
}
private static class FeedItemListAdapter extends EpisodeItemListAdapter {
diff --git a/app/src/main/java/de/danoeh/antennapod/view/ChapterSeekBar.java b/app/src/main/java/de/danoeh/antennapod/view/ChapterSeekBar.java
new file mode 100644
index 000000000..5e80198d5
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/view/ChapterSeekBar.java
@@ -0,0 +1,129 @@
+package de.danoeh.antennapod.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
+
+public class ChapterSeekBar extends androidx.appcompat.widget.AppCompatSeekBar {
+
+ private float top;
+ private float width;
+ private float bottom;
+ private float density;
+ private float progressPrimary;
+ private float progressSecondary;
+ private float[] dividerPos;
+ private final Paint paintBackground = new Paint();
+ private final Paint paintProgressPrimary = new Paint();
+ private final Paint paintProgressSecondary = new Paint();
+
+ public ChapterSeekBar(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public ChapterSeekBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public ChapterSeekBar(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ private void init(Context context) {
+ setBackground(null); // Removes the thumb shadow
+ dividerPos = null;
+ density = context.getResources().getDisplayMetrics().density;
+ paintBackground.setColor(ThemeUtils.getColorFromAttr(getContext(),
+ de.danoeh.antennapod.core.R.attr.currently_playing_background));
+ paintBackground.setAlpha(128);
+ paintProgressPrimary.setColor(ThemeUtils.getColorFromAttr(getContext(),
+ de.danoeh.antennapod.core.R.attr.colorPrimary));
+ paintProgressSecondary.setColor(ThemeUtils.getColorFromAttr(getContext(),
+ de.danoeh.antennapod.core.R.attr.seek_background));
+ }
+
+ /**
+ * Sets the relative positions of the chapter dividers.
+ * @param dividerPos of the chapter dividers relative to the duration of the media.
+ */
+ public void setDividerPos(final float[] dividerPos) {
+ if (dividerPos != null) {
+ this.dividerPos = new float[dividerPos.length + 2];
+ this.dividerPos[0] = 0;
+ System.arraycopy(dividerPos, 0, this.dividerPos, 1, dividerPos.length);
+ this.dividerPos[this.dividerPos.length - 1] = 1;
+ } else {
+ this.dividerPos = null;
+ }
+ }
+
+ @Override
+ protected synchronized void onDraw(Canvas canvas) {
+ top = getTop() + density * 7.5f;
+ bottom = getBottom() - density * 7.5f;
+ width = (float) (getRight() - getPaddingRight() - getLeft() - getPaddingLeft());
+ progressSecondary = getSecondaryProgress() / (float) getMax() * width;
+ progressPrimary = getProgress() / (float) getMax() * width;
+
+ if (dividerPos == null) {
+ drawProgress(canvas);
+ } else {
+ drawProgressChapters(canvas);
+ }
+ drawThumb(canvas);
+ }
+
+ private void drawProgress(Canvas canvas) {
+ final int saveCount = canvas.save();
+ canvas.translate(getPaddingLeft(), getPaddingTop());
+ canvas.drawRect(0, top, width, bottom, paintBackground);
+ canvas.drawRect(0, top, progressSecondary, bottom, paintProgressSecondary);
+ canvas.drawRect(0, top, progressPrimary, bottom, paintProgressPrimary);
+ canvas.restoreToCount(saveCount);
+ }
+
+ private void drawProgressChapters(Canvas canvas) {
+ final int saveCount = canvas.save();
+ int currChapter = 1;
+ float chapterMargin = density * 0.6f;
+ float topExpanded = getTop() + density * 7;
+ float bottomExpanded = getBottom() - density * 7;
+
+ canvas.translate(getPaddingLeft(), getPaddingTop());
+
+ for (int i = 1; i < dividerPos.length; i++) {
+ float right = dividerPos[i] * width - chapterMargin;
+ float left = dividerPos[i - 1] * width + chapterMargin;
+ float rightCurr = dividerPos[currChapter] * width - chapterMargin;
+ float leftCurr = dividerPos[currChapter - 1] * width + chapterMargin;
+
+ canvas.drawRect(left, top, right, bottom, paintBackground);
+
+ if (right < progressPrimary) {
+ currChapter = i + 1;
+ canvas.drawRect(left, top, right, bottom, paintProgressPrimary);
+ } else if (isPressed()) {
+ canvas.drawRect(leftCurr, topExpanded, rightCurr, bottomExpanded, paintBackground);
+ canvas.drawRect(leftCurr, topExpanded, progressPrimary, bottomExpanded, paintProgressPrimary);
+ } else {
+ if (progressSecondary > leftCurr) {
+ canvas.drawRect(leftCurr, top, progressSecondary, bottom, paintProgressSecondary);
+ }
+ canvas.drawRect(leftCurr, top, progressPrimary, bottom, paintProgressPrimary);
+ }
+ }
+ canvas.restoreToCount(saveCount);
+ }
+
+ private void drawThumb(Canvas canvas) {
+ final int saveCount = canvas.save();
+ canvas.translate(getPaddingLeft() - getThumbOffset(), getPaddingTop());
+ getThumb().draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+}
diff --git a/app/src/main/res/layout/audioplayer_fragment.xml b/app/src/main/res/layout/audioplayer_fragment.xml
index 62e0a5e3a..f77e96338 100644
--- a/app/src/main/res/layout/audioplayer_fragment.xml
+++ b/app/src/main/res/layout/audioplayer_fragment.xml
@@ -88,7 +88,7 @@
android:layoutDirection="ltr"
android:orientation="vertical">
- <SeekBar
+ <de.danoeh.antennapod.view.ChapterSeekBar
android:id="@+id/sbPosition"
android:layout_width="match_parent"
android:layout_height="wrap_content"