Skip to content

Commit a476ce2

Browse files
authored
Chapter dividers for the progress bar (#4915)
1 parent d444e8d commit a476ce2

File tree

4 files changed

+169
-10
lines changed

4 files changed

+169
-10
lines changed

app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import de.danoeh.antennapod.activity.MainActivity;
3030
import de.danoeh.antennapod.core.event.FavoritesEvent;
3131
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
32+
import de.danoeh.antennapod.core.feed.Chapter;
3233
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
3334
import de.danoeh.antennapod.core.feed.FeedItem;
3435
import de.danoeh.antennapod.core.feed.FeedMedia;
@@ -46,6 +47,7 @@
4647
import de.danoeh.antennapod.dialog.SleepTimerDialog;
4748
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
4849
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
50+
import de.danoeh.antennapod.view.ChapterSeekBar;
4951
import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView;
5052
import io.reactivex.Maybe;
5153
import io.reactivex.android.schedulers.AndroidSchedulers;
@@ -63,7 +65,7 @@
6365
* Shows the audio player.
6466
*/
6567
public class AudioPlayerFragment extends Fragment implements
66-
SeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
68+
ChapterSeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
6769
public static final String TAG = "AudioPlayerFragment";
6870
private static final int POS_COVER = 0;
6971
private static final int POS_DESCR = 1;
@@ -77,7 +79,7 @@ public class AudioPlayerFragment extends Fragment implements
7779
private ViewPager2 pager;
7880
private TextView txtvPosition;
7981
private TextView txtvLength;
80-
private SeekBar sbPosition;
82+
private ChapterSeekBar sbPosition;
8183
private ImageButton butRev;
8284
private TextView txtvRev;
8385
private ImageButton butPlay;
@@ -172,12 +174,33 @@ public void onPageSelected(int position) {
172174
return root;
173175
}
174176

175-
public void setHasChapters(boolean hasChapters) {
177+
private void setHasChapters(boolean hasChapters) {
176178
this.hasChapters = hasChapters;
177179
tabLayoutMediator.detach();
178180
tabLayoutMediator.attach();
179181
}
180182

183+
private void setChapterDividers(Playable media) {
184+
185+
if (media == null) {
186+
return;
187+
}
188+
189+
float[] dividerPos = null;
190+
191+
if (hasChapters) {
192+
List<Chapter> chapters = media.getChapters();
193+
dividerPos = new float[chapters.size()];
194+
float duration = media.getDuration();
195+
196+
for (int i = 0; i < chapters.size(); i++) {
197+
dividerPos[i] = chapters.get(i).getStart() / duration;
198+
}
199+
}
200+
201+
sbPosition.setDividerPos(dividerPos);
202+
}
203+
181204
public View getExternalPlayerHolder() {
182205
return getView().findViewById(R.id.playerFragment);
183206
}
@@ -298,16 +321,17 @@ private void loadMediaInfo() {
298321
disposable = Maybe.create(emitter -> {
299322
Playable media = controller.getMedia();
300323
if (media != null) {
324+
media.loadChapterMarks(getContext());
301325
emitter.onSuccess(media);
302326
} else {
303327
emitter.onComplete();
304328
}
305329
})
306-
.subscribeOn(Schedulers.io())
307-
.observeOn(AndroidSchedulers.mainThread())
308-
.subscribe(media -> updateUi((Playable) media),
309-
error -> Log.e(TAG, Log.getStackTraceString(error)),
310-
() -> updateUi(null));
330+
.subscribeOn(Schedulers.io())
331+
.observeOn(AndroidSchedulers.mainThread())
332+
.subscribe(media -> updateUi((Playable) media),
333+
error -> Log.e(TAG, Log.getStackTraceString(error)),
334+
() -> updateUi(null));
311335
}
312336

313337
private PlaybackController newPlaybackController() {
@@ -389,8 +413,15 @@ private void updateUi(Playable media) {
389413
if (controller == null) {
390414
return;
391415
}
416+
417+
if (media != null && media.getChapters() != null) {
418+
setHasChapters(media.getChapters().size() > 0);
419+
} else {
420+
setHasChapters(false);
421+
}
392422
updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration()));
393423
updatePlaybackSpeedButton(media);
424+
setChapterDividers(media);
394425
setupOptionsMenu(media);
395426
}
396427

app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ private void onMediaChanged(Playable media) {
137137
return;
138138
}
139139
adapter.setMedia(media);
140-
((AudioPlayerFragment) getParentFragment()).setHasChapters(adapter.getItemCount() > 0);
141140
int positionOfCurrentChapter = getCurrentChapter(media);
142141
updateChapterSelection(positionOfCurrentChapter);
143142
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package de.danoeh.antennapod.view;
2+
3+
import android.content.Context;
4+
import android.graphics.Canvas;
5+
import android.graphics.Paint;
6+
import android.util.AttributeSet;
7+
import de.danoeh.antennapod.ui.common.ThemeUtils;
8+
9+
public class ChapterSeekBar extends androidx.appcompat.widget.AppCompatSeekBar {
10+
11+
private float top;
12+
private float width;
13+
private float bottom;
14+
private float density;
15+
private float progressPrimary;
16+
private float progressSecondary;
17+
private float[] dividerPos;
18+
private final Paint paintBackground = new Paint();
19+
private final Paint paintProgressPrimary = new Paint();
20+
private final Paint paintProgressSecondary = new Paint();
21+
22+
public ChapterSeekBar(Context context) {
23+
super(context);
24+
init(context);
25+
}
26+
27+
public ChapterSeekBar(Context context, AttributeSet attrs) {
28+
super(context, attrs);
29+
init(context);
30+
}
31+
32+
public ChapterSeekBar(Context context, AttributeSet attrs, int defStyle) {
33+
super(context, attrs, defStyle);
34+
init(context);
35+
}
36+
37+
private void init(Context context) {
38+
setBackground(null); // Removes the thumb shadow
39+
dividerPos = null;
40+
density = context.getResources().getDisplayMetrics().density;
41+
paintBackground.setColor(ThemeUtils.getColorFromAttr(getContext(),
42+
de.danoeh.antennapod.core.R.attr.currently_playing_background));
43+
paintBackground.setAlpha(128);
44+
paintProgressPrimary.setColor(ThemeUtils.getColorFromAttr(getContext(),
45+
de.danoeh.antennapod.core.R.attr.colorPrimary));
46+
paintProgressSecondary.setColor(ThemeUtils.getColorFromAttr(getContext(),
47+
de.danoeh.antennapod.core.R.attr.seek_background));
48+
}
49+
50+
/**
51+
* Sets the relative positions of the chapter dividers.
52+
* @param dividerPos of the chapter dividers relative to the duration of the media.
53+
*/
54+
public void setDividerPos(final float[] dividerPos) {
55+
if (dividerPos != null) {
56+
this.dividerPos = new float[dividerPos.length + 2];
57+
this.dividerPos[0] = 0;
58+
System.arraycopy(dividerPos, 0, this.dividerPos, 1, dividerPos.length);
59+
this.dividerPos[this.dividerPos.length - 1] = 1;
60+
} else {
61+
this.dividerPos = null;
62+
}
63+
}
64+
65+
@Override
66+
protected synchronized void onDraw(Canvas canvas) {
67+
top = getTop() + density * 7.5f;
68+
bottom = getBottom() - density * 7.5f;
69+
width = (float) (getRight() - getPaddingRight() - getLeft() - getPaddingLeft());
70+
progressSecondary = getSecondaryProgress() / (float) getMax() * width;
71+
progressPrimary = getProgress() / (float) getMax() * width;
72+
73+
if (dividerPos == null) {
74+
drawProgress(canvas);
75+
} else {
76+
drawProgressChapters(canvas);
77+
}
78+
drawThumb(canvas);
79+
}
80+
81+
private void drawProgress(Canvas canvas) {
82+
final int saveCount = canvas.save();
83+
canvas.translate(getPaddingLeft(), getPaddingTop());
84+
canvas.drawRect(0, top, width, bottom, paintBackground);
85+
canvas.drawRect(0, top, progressSecondary, bottom, paintProgressSecondary);
86+
canvas.drawRect(0, top, progressPrimary, bottom, paintProgressPrimary);
87+
canvas.restoreToCount(saveCount);
88+
}
89+
90+
private void drawProgressChapters(Canvas canvas) {
91+
final int saveCount = canvas.save();
92+
int currChapter = 1;
93+
float chapterMargin = density * 0.6f;
94+
float topExpanded = getTop() + density * 7;
95+
float bottomExpanded = getBottom() - density * 7;
96+
97+
canvas.translate(getPaddingLeft(), getPaddingTop());
98+
99+
for (int i = 1; i < dividerPos.length; i++) {
100+
float right = dividerPos[i] * width - chapterMargin;
101+
float left = dividerPos[i - 1] * width + chapterMargin;
102+
float rightCurr = dividerPos[currChapter] * width - chapterMargin;
103+
float leftCurr = dividerPos[currChapter - 1] * width + chapterMargin;
104+
105+
canvas.drawRect(left, top, right, bottom, paintBackground);
106+
107+
if (right < progressPrimary) {
108+
currChapter = i + 1;
109+
canvas.drawRect(left, top, right, bottom, paintProgressPrimary);
110+
} else if (isPressed()) {
111+
canvas.drawRect(leftCurr, topExpanded, rightCurr, bottomExpanded, paintBackground);
112+
canvas.drawRect(leftCurr, topExpanded, progressPrimary, bottomExpanded, paintProgressPrimary);
113+
} else {
114+
if (progressSecondary > leftCurr) {
115+
canvas.drawRect(leftCurr, top, progressSecondary, bottom, paintProgressSecondary);
116+
}
117+
canvas.drawRect(leftCurr, top, progressPrimary, bottom, paintProgressPrimary);
118+
}
119+
}
120+
canvas.restoreToCount(saveCount);
121+
}
122+
123+
private void drawThumb(Canvas canvas) {
124+
final int saveCount = canvas.save();
125+
canvas.translate(getPaddingLeft() - getThumbOffset(), getPaddingTop());
126+
getThumb().draw(canvas);
127+
canvas.restoreToCount(saveCount);
128+
}
129+
}

app/src/main/res/layout/audioplayer_fragment.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
android:layoutDirection="ltr"
8989
android:orientation="vertical">
9090

91-
<SeekBar
91+
<de.danoeh.antennapod.view.ChapterSeekBar
9292
android:id="@+id/sbPosition"
9393
android:layout_width="match_parent"
9494
android:layout_height="wrap_content"

0 commit comments

Comments
 (0)