Compare commits
6 Commits
d040500c1a
...
ba7ef9c90a
Author | SHA1 | Date |
---|---|---|
flofriday | ba7ef9c90a | |
ByteHamster | a61f548792 | |
flofriday | 2827f41430 | |
flofriday | 6f572faa77 | |
Simon Conrad | ba14510b80 | |
ByteHamster | cb1a03cd8d |
|
@ -19,6 +19,7 @@ import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.model.feed.Chapter;
|
import de.danoeh.antennapod.model.feed.Chapter;
|
||||||
import de.danoeh.antennapod.ui.common.Converter;
|
import de.danoeh.antennapod.ui.common.Converter;
|
||||||
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
|
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
|
||||||
|
import de.danoeh.antennapod.ui.common.ImagePlaceholder;
|
||||||
import de.danoeh.antennapod.ui.common.IntentUtils;
|
import de.danoeh.antennapod.ui.common.IntentUtils;
|
||||||
import de.danoeh.antennapod.model.playback.Playable;
|
import de.danoeh.antennapod.model.playback.Playable;
|
||||||
import de.danoeh.antennapod.ui.common.CircularProgressBar;
|
import de.danoeh.antennapod.ui.common.CircularProgressBar;
|
||||||
|
@ -99,15 +100,27 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
|
||||||
|
|
||||||
if (hasImages) {
|
if (hasImages) {
|
||||||
holder.image.setVisibility(View.VISIBLE);
|
holder.image.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
float radius = 4 * context.getResources().getDisplayMetrics().density;
|
||||||
|
RequestOptions options = new RequestOptions()
|
||||||
|
.placeholder(ImagePlaceholder.getDrawable(context, radius))
|
||||||
|
.dontAnimate()
|
||||||
|
.transform(new FitCenter(), new RoundedCorners((int) radius));
|
||||||
|
|
||||||
if (TextUtils.isEmpty(sc.getImageUrl())) {
|
if (TextUtils.isEmpty(sc.getImageUrl())) {
|
||||||
Glide.with(context).clear(holder.image);
|
if (media.getImageLocation() == null) {
|
||||||
|
Glide.with(context).clear(holder.image);
|
||||||
|
holder.image.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
Glide.with(context)
|
||||||
|
.load(media.getImageLocation())
|
||||||
|
.apply(options)
|
||||||
|
.into(holder.image);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
.load(EmbeddedChapterImage.getModelFor(media, position))
|
.load(EmbeddedChapterImage.getModelFor(media, position))
|
||||||
.apply(new RequestOptions()
|
.apply(options)
|
||||||
.dontAnimate()
|
|
||||||
.transform(new FitCenter(), new RoundedCorners((int)
|
|
||||||
(4 * context.getResources().getDisplayMetrics().density))))
|
|
||||||
.into(holder.image);
|
.into(holder.image);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -15,8 +15,6 @@ import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
@ -27,13 +25,12 @@ import androidx.documentfile.provider.DocumentFile;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.request.RequestOptions;
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
import com.google.android.material.appbar.AppBarLayout;
|
|
||||||
import com.google.android.material.appbar.CollapsingToolbarLayout;
|
|
||||||
import com.google.android.material.appbar.MaterialToolbar;
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.activity.MainActivity;
|
import de.danoeh.antennapod.activity.MainActivity;
|
||||||
|
import de.danoeh.antennapod.databinding.FeedinfoBinding;
|
||||||
import de.danoeh.antennapod.ui.TransitionEffect;
|
import de.danoeh.antennapod.ui.TransitionEffect;
|
||||||
import de.danoeh.antennapod.storage.database.DBReader;
|
import de.danoeh.antennapod.storage.database.DBReader;
|
||||||
import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
|
import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
|
||||||
|
@ -69,17 +66,7 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
|
||||||
|
|
||||||
private Feed feed;
|
private Feed feed;
|
||||||
private Disposable disposable;
|
private Disposable disposable;
|
||||||
private ImageView imgvCover;
|
private FeedinfoBinding viewBinding;
|
||||||
private TextView txtvTitle;
|
|
||||||
private TextView txtvDescription;
|
|
||||||
private TextView txtvFundingUrl;
|
|
||||||
private TextView lblSupport;
|
|
||||||
private TextView txtvUrl;
|
|
||||||
private TextView txtvAuthorHeader;
|
|
||||||
private ImageView imgvBackground;
|
|
||||||
private View infoContainer;
|
|
||||||
private View header;
|
|
||||||
private MaterialToolbar toolbar;
|
|
||||||
|
|
||||||
public static FeedInfoFragment newInstance(Feed feed) {
|
public static FeedInfoFragment newInstance(Feed feed) {
|
||||||
FeedInfoFragment fragment = new FeedInfoFragment();
|
FeedInfoFragment fragment = new FeedInfoFragment();
|
||||||
|
@ -110,58 +97,45 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
View root = inflater.inflate(R.layout.feedinfo, null);
|
viewBinding = FeedinfoBinding.inflate(inflater);
|
||||||
toolbar = root.findViewById(R.id.toolbar);
|
viewBinding.toolbar.setTitle("");
|
||||||
toolbar.setTitle("");
|
viewBinding.toolbar.inflateMenu(R.menu.feedinfo);
|
||||||
toolbar.inflateMenu(R.menu.feedinfo);
|
viewBinding.toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
|
||||||
toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
|
viewBinding.toolbar.setOnMenuItemClickListener(this);
|
||||||
toolbar.setOnMenuItemClickListener(this);
|
|
||||||
refreshToolbarState();
|
refreshToolbarState();
|
||||||
|
|
||||||
AppBarLayout appBar = root.findViewById(R.id.appBar);
|
ToolbarIconTintManager iconTintManager = new ToolbarIconTintManager(getContext(),
|
||||||
CollapsingToolbarLayout collapsingToolbar = root.findViewById(R.id.collapsing_toolbar);
|
viewBinding.toolbar, viewBinding.collapsingToolbar) {
|
||||||
ToolbarIconTintManager iconTintManager = new ToolbarIconTintManager(getContext(), toolbar, collapsingToolbar) {
|
|
||||||
@Override
|
@Override
|
||||||
protected void doTint(Context themedContext) {
|
protected void doTint(Context themedContext) {
|
||||||
toolbar.getMenu().findItem(R.id.visit_website_item)
|
viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item)
|
||||||
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_web));
|
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_web));
|
||||||
toolbar.getMenu().findItem(R.id.share_item)
|
viewBinding.toolbar.getMenu().findItem(R.id.share_item)
|
||||||
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_share));
|
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_share));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
iconTintManager.updateTint();
|
iconTintManager.updateTint();
|
||||||
appBar.addOnOffsetChangedListener(iconTintManager);
|
viewBinding.appBar.addOnOffsetChangedListener(iconTintManager);
|
||||||
|
|
||||||
imgvCover = root.findViewById(R.id.imgvCover);
|
viewBinding.header.butShowInfo.setVisibility(View.INVISIBLE);
|
||||||
txtvTitle = root.findViewById(R.id.txtvTitle);
|
viewBinding.header.butShowSettings.setVisibility(View.INVISIBLE);
|
||||||
txtvAuthorHeader = root.findViewById(R.id.txtvAuthor);
|
viewBinding.header.butFilter.setVisibility(View.INVISIBLE);
|
||||||
imgvBackground = root.findViewById(R.id.imgvBackground);
|
|
||||||
header = root.findViewById(R.id.headerContainer);
|
|
||||||
infoContainer = root.findViewById(R.id.infoContainer);
|
|
||||||
root.findViewById(R.id.butShowInfo).setVisibility(View.INVISIBLE);
|
|
||||||
root.findViewById(R.id.butShowSettings).setVisibility(View.INVISIBLE);
|
|
||||||
root.findViewById(R.id.butFilter).setVisibility(View.INVISIBLE);
|
|
||||||
// https://github.com/bumptech/glide/issues/529
|
// https://github.com/bumptech/glide/issues/529
|
||||||
imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000));
|
viewBinding.imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000));
|
||||||
|
|
||||||
txtvDescription = root.findViewById(R.id.txtvDescription);
|
viewBinding.urlLabel.setOnClickListener(copyUrlToClipboard);
|
||||||
txtvUrl = root.findViewById(R.id.txtvUrl);
|
|
||||||
lblSupport = root.findViewById(R.id.lblSupport);
|
|
||||||
txtvFundingUrl = root.findViewById(R.id.txtvFundingUrl);
|
|
||||||
|
|
||||||
txtvUrl.setOnClickListener(copyUrlToClipboard);
|
|
||||||
|
|
||||||
long feedId = getArguments().getLong(EXTRA_FEED_ID);
|
long feedId = getArguments().getLong(EXTRA_FEED_ID);
|
||||||
getParentFragmentManager().beginTransaction().replace(R.id.statisticsFragmentContainer,
|
getParentFragmentManager().beginTransaction().replace(R.id.statisticsFragmentContainer,
|
||||||
FeedStatisticsFragment.newInstance(feedId, false), "feed_statistics_fragment")
|
FeedStatisticsFragment.newInstance(feedId, false), "feed_statistics_fragment")
|
||||||
.commitAllowingStateLoss();
|
.commitAllowingStateLoss();
|
||||||
|
|
||||||
root.findViewById(R.id.btnvOpenStatistics).setOnClickListener(view -> {
|
viewBinding.statisticsButton.setOnClickListener(view -> {
|
||||||
StatisticsFragment fragment = new StatisticsFragment();
|
StatisticsFragment fragment = new StatisticsFragment();
|
||||||
((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE);
|
((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE);
|
||||||
});
|
});
|
||||||
|
|
||||||
return root;
|
return viewBinding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -186,13 +160,14 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
if (header == null || infoContainer == null) {
|
if (viewBinding == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int horizontalSpacing = (int) getResources().getDimension(R.dimen.additional_horizontal_spacing);
|
int horizontalSpacing = (int) getResources().getDimension(R.dimen.additional_horizontal_spacing);
|
||||||
header.setPadding(horizontalSpacing, header.getPaddingTop(), horizontalSpacing, header.getPaddingBottom());
|
viewBinding.header.getRoot().setPadding(horizontalSpacing, viewBinding.header.getRoot().getPaddingTop(),
|
||||||
infoContainer.setPadding(horizontalSpacing, infoContainer.getPaddingTop(),
|
horizontalSpacing, viewBinding.header.getRoot().getPaddingBottom());
|
||||||
horizontalSpacing, infoContainer.getPaddingBottom());
|
viewBinding.infoContainer.setPadding(horizontalSpacing, viewBinding.infoContainer.getPaddingTop(),
|
||||||
|
horizontalSpacing, viewBinding.infoContainer.getPaddingBottom());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showFeed() {
|
private void showFeed() {
|
||||||
|
@ -206,7 +181,7 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
|
||||||
.error(R.color.light_gray)
|
.error(R.color.light_gray)
|
||||||
.fitCenter()
|
.fitCenter()
|
||||||
.dontAnimate())
|
.dontAnimate())
|
||||||
.into(imgvCover);
|
.into(viewBinding.header.imgvCover);
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.load(feed.getImageUrl())
|
.load(feed.getImageUrl())
|
||||||
.apply(new RequestOptions()
|
.apply(new RequestOptions()
|
||||||
|
@ -214,27 +189,26 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
|
||||||
.error(R.color.image_readability_tint)
|
.error(R.color.image_readability_tint)
|
||||||
.transform(new FastBlurTransformation())
|
.transform(new FastBlurTransformation())
|
||||||
.dontAnimate())
|
.dontAnimate())
|
||||||
.into(imgvBackground);
|
.into(viewBinding.imgvBackground);
|
||||||
|
|
||||||
txtvTitle.setText(feed.getTitle());
|
viewBinding.header.txtvTitle.setText(feed.getTitle());
|
||||||
txtvTitle.setMaxLines(6);
|
viewBinding.header.txtvTitle.setMaxLines(6);
|
||||||
|
|
||||||
String description = HtmlToPlainText.getPlainText(feed.getDescription());
|
String description = HtmlToPlainText.getPlainText(feed.getDescription());
|
||||||
|
|
||||||
txtvDescription.setText(description);
|
viewBinding.descriptionLabel.setText(description);
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(feed.getAuthor())) {
|
if (!TextUtils.isEmpty(feed.getAuthor())) {
|
||||||
txtvAuthorHeader.setText(feed.getAuthor());
|
viewBinding.header.txtvAuthor.setText(feed.getAuthor());
|
||||||
}
|
}
|
||||||
|
|
||||||
txtvUrl.setText(feed.getDownloadUrl());
|
viewBinding.urlLabel.setText(feed.getDownloadUrl());
|
||||||
txtvUrl.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0);
|
viewBinding.urlLabel.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0);
|
||||||
|
|
||||||
if (feed.getPaymentLinks() == null || feed.getPaymentLinks().size() == 0) {
|
if (feed.getPaymentLinks() == null || feed.getPaymentLinks().size() == 0) {
|
||||||
lblSupport.setVisibility(View.GONE);
|
viewBinding.supportHeadingLabel.setVisibility(View.GONE);
|
||||||
txtvFundingUrl.setVisibility(View.GONE);
|
viewBinding.supportUrl.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
lblSupport.setVisibility(View.VISIBLE);
|
|
||||||
ArrayList<FeedFunding> fundingList = feed.getPaymentLinks();
|
ArrayList<FeedFunding> fundingList = feed.getPaymentLinks();
|
||||||
|
|
||||||
// Filter for duplicates, but keep items in the order that they have in the feed.
|
// Filter for duplicates, but keep items in the order that they have in the feed.
|
||||||
|
@ -260,7 +234,7 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
|
||||||
str.append("\n");
|
str.append("\n");
|
||||||
}
|
}
|
||||||
str = new StringBuilder(StringUtils.trim(str.toString()));
|
str = new StringBuilder(StringUtils.trim(str.toString()));
|
||||||
txtvFundingUrl.setText(str.toString());
|
viewBinding.supportUrl.setText(str.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshToolbarState();
|
refreshToolbarState();
|
||||||
|
@ -275,11 +249,13 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshToolbarState() {
|
private void refreshToolbarState() {
|
||||||
toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(feed != null && feed.isLocalFeed());
|
viewBinding.toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(
|
||||||
toolbar.getMenu().findItem(R.id.share_item).setVisible(feed != null && !feed.isLocalFeed());
|
feed != null && feed.isLocalFeed());
|
||||||
toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null
|
viewBinding.toolbar.getMenu().findItem(R.id.share_item).setVisible(feed != null && !feed.isLocalFeed());
|
||||||
|
viewBinding.toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null
|
||||||
|
&& feed.getLink() != null
|
||||||
&& IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink()))));
|
&& IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink()))));
|
||||||
toolbar.getMenu().findItem(R.id.edit_feed_url_item).setVisible(feed != null && !feed.isLocalFeed());
|
viewBinding.toolbar.getMenu().findItem(R.id.edit_feed_url_item).setVisible(feed != null && !feed.isLocalFeed());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -310,8 +286,9 @@ public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenu
|
||||||
@Override
|
@Override
|
||||||
protected void setUrl(String url) {
|
protected void setUrl(String url) {
|
||||||
feed.setDownloadUrl(url);
|
feed.setDownloadUrl(url);
|
||||||
txtvUrl.setText(feed.getDownloadUrl());
|
viewBinding.urlLabel.setText(feed.getDownloadUrl());
|
||||||
txtvUrl.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0);
|
viewBinding.urlLabel.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||||
|
0, 0, R.drawable.ic_paperclip, 0);
|
||||||
}
|
}
|
||||||
}.show();
|
}.show();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -157,7 +157,7 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
|
||||||
viewBinding.progressBar.setVisibility(View.VISIBLE);
|
viewBinding.progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
ToolbarIconTintManager iconTintManager = new ToolbarIconTintManager(
|
ToolbarIconTintManager iconTintManager = new ToolbarIconTintManager(
|
||||||
getContext(), viewBinding.toolbar, viewBinding.collapsingToolbar) {
|
viewBinding.toolbar.getContext(), viewBinding.toolbar, viewBinding.collapsingToolbar) {
|
||||||
@Override
|
@Override
|
||||||
protected void doTint(Context themedContext) {
|
protected void doTint(Context themedContext) {
|
||||||
viewBinding.toolbar.getMenu().findItem(R.id.refresh_item)
|
viewBinding.toolbar.getMenu().findItem(R.id.refresh_item)
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
|
||||||
app:title="@string/add_feed_label"
|
app:title="@string/add_feed_label"
|
||||||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||||
app:navigationIcon="?homeAsUpIndicator" />
|
app:navigationIcon="?homeAsUpIndicator" />
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
|
||||||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||||
app:navigationIcon="@drawable/ic_arrow_down" />
|
app:navigationIcon="@drawable/ic_arrow_down" />
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
app:title="@string/downloads_log_label" />
|
app:title="@string/downloads_log_label" />
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentTop="true"
|
android:layout_alignParentTop="true"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
|
||||||
app:layout_collapseMode="pin"
|
app:layout_collapseMode="pin"
|
||||||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||||
app:navigationIcon="?homeAsUpIndicator" />
|
app:navigationIcon="?homeAsUpIndicator" />
|
||||||
|
@ -69,70 +68,7 @@
|
||||||
android:paddingHorizontal="@dimen/additional_horizontal_spacing">
|
android:paddingHorizontal="@dimen/additional_horizontal_spacing">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/lblUrl"
|
android:id="@+id/statisticsHeadingLabel"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:text="@string/url_label"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
tools:background="@android:color/holo_red_light" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/txtvUrl"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:maxLines="4"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:paddingBottom="4dp"
|
|
||||||
android:drawablePadding="4dp"
|
|
||||||
tools:background="@android:color/holo_green_dark"
|
|
||||||
tools:text="http://www.example.com/feed" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/lblSupport"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:text="@string/support_funding_label"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
android:textSize="18sp"
|
|
||||||
tools:background="@android:color/holo_red_light" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/txtvFundingUrl"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:maxLines="8"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:paddingBottom="4dp"
|
|
||||||
android:linksClickable="true"
|
|
||||||
android:autoLink="web"
|
|
||||||
tools:background="@android:color/holo_green_dark" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:text="@string/description_label"
|
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
|
||||||
tools:background="@android:color/holo_red_light" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/txtvDescription"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/design_time_lorem_ipsum"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
tools:background="@android:color/holo_green_dark" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/lblStatistics"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
|
@ -148,7 +84,7 @@
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnvOpenStatistics"
|
android:id="@+id/statisticsButton"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minWidth="0dp"
|
android:minWidth="0dp"
|
||||||
|
@ -156,6 +92,70 @@
|
||||||
android:text="@string/statistics_view_all"
|
android:text="@string/statistics_view_all"
|
||||||
style="@style/Widget.MaterialComponents.Button.TextButton" />
|
style="@style/Widget.MaterialComponents.Button.TextButton" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/supportHeadingLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:text="@string/support_funding_label"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:background="@android:color/holo_red_light" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/supportUrl"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxLines="8"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:linksClickable="true"
|
||||||
|
android:autoLink="web"
|
||||||
|
tools:background="@android:color/holo_green_dark" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/descriptionHeadingLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:text="@string/description_label"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
tools:background="@android:color/holo_red_light" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/descriptionLabel"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/design_time_lorem_ipsum"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
tools:background="@android:color/holo_green_dark" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/urlHeadingLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:text="@string/url_label"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
|
tools:background="@android:color/holo_red_light" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/urlLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:maxLines="4"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:drawablePadding="4dp"
|
||||||
|
tools:background="@android:color/holo_green_dark"
|
||||||
|
tools:text="http://www.example.com/feed" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
|
||||||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||||
app:navigationIcon="?homeAsUpIndicator" />
|
app:navigationIcon="?homeAsUpIndicator" />
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
|
||||||
android:elevation="4dp"
|
|
||||||
app:title="@string/feed_settings_label"
|
app:title="@string/feed_settings_label"
|
||||||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||||
app:navigationIcon="?homeAsUpIndicator" />
|
app:navigationIcon="?homeAsUpIndicator" />
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
|
||||||
app:title="@string/search_label"
|
app:title="@string/search_label"
|
||||||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||||
app:navigationIcon="?homeAsUpIndicator" />
|
app:navigationIcon="?homeAsUpIndicator" />
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
|
||||||
android:layout_alignParentTop="true" />
|
android:layout_alignParentTop="true" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|
|
@ -4,7 +4,7 @@ import java.util.List;
|
||||||
|
|
||||||
public class Chapter {
|
public class Chapter {
|
||||||
private long id;
|
private long id;
|
||||||
/** Defines starting point in milliseconds. */
|
/** The start time of the chapter in milliseconds */
|
||||||
private long start;
|
private long start;
|
||||||
private String title;
|
private String title;
|
||||||
private String link;
|
private String link;
|
||||||
|
@ -66,7 +66,7 @@ public class Chapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ID3Chapter [title=" + getTitle() + ", start=" + getStart() + ", url=" + getLink() + "]";
|
return "Chapter [title=" + getTitle() + ", start=" + getStart() + ", url=" + getLink() + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getId() {
|
public long getId() {
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
package de.danoeh.antennapod.parser.media.m4a;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.model.feed.Chapter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
public class M4AChapterReader {
|
||||||
|
private static final String TAG = "M4AChapterReader";
|
||||||
|
private final List<Chapter> chapters = new ArrayList<>();
|
||||||
|
private final InputStream inputStream;
|
||||||
|
private static final int FTYP_CODE = 0x66747970; // "ftyp"
|
||||||
|
|
||||||
|
public M4AChapterReader(InputStream input) {
|
||||||
|
inputStream = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the input stream populating the chapters list
|
||||||
|
*/
|
||||||
|
public void readInputStream() {
|
||||||
|
try {
|
||||||
|
isM4A(inputStream);
|
||||||
|
int dataSize = this.findAtom("moov.udta.chpl");
|
||||||
|
if (dataSize == -1) {
|
||||||
|
Log.d(TAG, "Nero Chapter Atom not found");
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Nero Chapter Atom found. Data Size: " + dataSize);
|
||||||
|
this.parseNeroChapterAtom(dataSize);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(TAG, "ERROR: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the atom with the given name in the M4A file
|
||||||
|
*
|
||||||
|
* @param name the name of the atom to find, separated by dots
|
||||||
|
* @return the size of the atom (minus the 8-byte header) if found
|
||||||
|
* @throws IOException if an I/O error occurs or the atom is not found
|
||||||
|
*/
|
||||||
|
public int findAtom(String name) throws IOException {
|
||||||
|
// Split the name into parts encoded as UTF-8
|
||||||
|
String[] parts = name.split("\\.");
|
||||||
|
int partIndex = 0;
|
||||||
|
// Initialize remaining size to track the current part's size and check if it is exceeded
|
||||||
|
int remainingSize = -1;
|
||||||
|
|
||||||
|
// Read the M4A file atom by atom
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
while (true) {
|
||||||
|
// Read the atom header
|
||||||
|
IOUtils.readFully(inputStream, buffer.array());
|
||||||
|
// Get the size of the current atom
|
||||||
|
int chunkSize = buffer.getInt();
|
||||||
|
int dataSize = chunkSize - 8;
|
||||||
|
|
||||||
|
// Get the atom type
|
||||||
|
String atomType = StandardCharsets.UTF_8.decode(buffer).toString();
|
||||||
|
|
||||||
|
// Reset the buffer for reading the atom data
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
// Check if the current atom matches the current part of the name
|
||||||
|
if (atomType.equals(parts[partIndex])) {
|
||||||
|
if (partIndex == parts.length - 1) {
|
||||||
|
// If the current atom is the last part of the name return its size
|
||||||
|
return dataSize;
|
||||||
|
} else {
|
||||||
|
// Else move to the next part of the name
|
||||||
|
partIndex++;
|
||||||
|
// Update the remaining size
|
||||||
|
remainingSize = dataSize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Do not check the remaining size of top-level atoms
|
||||||
|
if (partIndex > 0) {
|
||||||
|
// Update the remaining size
|
||||||
|
remainingSize -= dataSize;
|
||||||
|
// If the remaining size is exhausted, throw an exception
|
||||||
|
if (remainingSize <= 0) {
|
||||||
|
throw new IOException("Part size exceeded for part \"" + parts[partIndex - 1]
|
||||||
|
+ "\" while searching atom. Remaining Size: " + remainingSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Skip the rest of the atom
|
||||||
|
IOUtils.skipFully(inputStream, dataSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the Nero Chapter Atom in the M4A file
|
||||||
|
* Assumes that the current position is at the start of the Nero Chapter Atom
|
||||||
|
*
|
||||||
|
* @param chunkSize the size of the Nero Chapter Atom
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
* @see <a href="https://github.com/Zeugma440/atldotnet/wiki/Focus-on-Chapter-metadata#nero-chapters">Nero Chapter</a>
|
||||||
|
*/
|
||||||
|
private void parseNeroChapterAtom(long chunkSize) throws IOException {
|
||||||
|
// Read the Nero Chapter Atom data into a buffer
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.allocate((int) chunkSize).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
IOUtils.readFully(inputStream, byteBuffer.array());
|
||||||
|
// Skip the 5-byte header
|
||||||
|
// Nero Chapter Atom consists of a 5-byte header followed by chapter data
|
||||||
|
// The first 4 bytes are the version and flags, the 5th byte is reserved
|
||||||
|
byteBuffer.position(5);
|
||||||
|
// Get the chapter count
|
||||||
|
int chapterCount = byteBuffer.getInt();
|
||||||
|
Log.d(TAG, "Nero Chapter Count: " + chapterCount);
|
||||||
|
|
||||||
|
// Parse each chapter
|
||||||
|
for (int i = 0; i < chapterCount; i++) {
|
||||||
|
long startTime = byteBuffer.getLong();
|
||||||
|
int chapterNameSize = byteBuffer.get();
|
||||||
|
byte[] chapterNameBytes = new byte[chapterNameSize];
|
||||||
|
byteBuffer.get(chapterNameBytes, 0, chapterNameSize);
|
||||||
|
String chapterName = new String(chapterNameBytes, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
Chapter chapter = new Chapter();
|
||||||
|
chapter.setStart(startTime / 10000);
|
||||||
|
chapter.setTitle(chapterName);
|
||||||
|
chapter.setChapterId(String.valueOf(i + 1));
|
||||||
|
chapters.add(chapter);
|
||||||
|
|
||||||
|
Log.d(TAG, "Nero Chapter " + (i + 1) + ": " + chapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Chapter> getChapters() {
|
||||||
|
return chapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the input stream is an M4A file by checking the signature
|
||||||
|
*
|
||||||
|
* @param inputStream the input stream to check
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public static void isM4A(InputStream inputStream) throws IOException {
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
IOUtils.readFully(inputStream, byteBuffer.array());
|
||||||
|
|
||||||
|
int ftypSize = byteBuffer.getInt();
|
||||||
|
if (byteBuffer.getInt() != FTYP_CODE) {
|
||||||
|
throw new IOException("Not an M4A file");
|
||||||
|
}
|
||||||
|
IOUtils.skipFully(inputStream, ftypSize - 8);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package de.danoeh.antennapod.parser.media.m4a;
|
||||||
|
|
||||||
|
import de.danoeh.antennapod.model.feed.Chapter;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public class M4AChapterReaderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFiles() throws IOException {
|
||||||
|
testFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFile() throws IOException {
|
||||||
|
InputStream inputStream = getClass().getClassLoader()
|
||||||
|
.getResource("nero-chapters.m4a").openStream();
|
||||||
|
M4AChapterReader reader = new M4AChapterReader(inputStream);
|
||||||
|
reader.readInputStream();
|
||||||
|
List<Chapter> chapters = reader.getChapters();
|
||||||
|
|
||||||
|
assertEquals(4, chapters.size());
|
||||||
|
|
||||||
|
assertEquals(0, chapters.get(0).getStart());
|
||||||
|
assertEquals(3000, chapters.get(1).getStart());
|
||||||
|
assertEquals(6000, chapters.get(2).getStart());
|
||||||
|
assertEquals(9000, chapters.get(3).getStart());
|
||||||
|
|
||||||
|
assertEquals("Chapter 1 - ❤️😊", chapters.get(0).getTitle());
|
||||||
|
assertEquals("Chapter 2 - ßöÄ", chapters.get(1).getTitle());
|
||||||
|
assertEquals("Chapter 3 - 爱", chapters.get(2).getTitle());
|
||||||
|
assertEquals("Chapter 4", chapters.get(3).getTitle());
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
|
@ -16,6 +16,7 @@ import de.danoeh.antennapod.parser.media.id3.ID3ReaderException;
|
||||||
import de.danoeh.antennapod.model.playback.Playable;
|
import de.danoeh.antennapod.model.playback.Playable;
|
||||||
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentChapterReader;
|
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentChapterReader;
|
||||||
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentReaderException;
|
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentReaderException;
|
||||||
|
import de.danoeh.antennapod.parser.media.m4a.M4AChapterReader;
|
||||||
import okhttp3.CacheControl;
|
import okhttp3.CacheControl;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
@ -106,6 +107,19 @@ public class ChapterUtils {
|
||||||
} catch (IOException | VorbisCommentReaderException e) {
|
} catch (IOException | VorbisCommentReaderException e) {
|
||||||
Log.e(TAG, "Unable to load vorbis chapters: " + e.getMessage());
|
Log.e(TAG, "Unable to load vorbis chapters: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try (CountingInputStream in = openStream(playable, context)) {
|
||||||
|
List<Chapter> chapters = readM4AChaptersFromInputStream(in);
|
||||||
|
if (!chapters.isEmpty()) {
|
||||||
|
Log.i(TAG, "Chapters loaded");
|
||||||
|
return chapters;
|
||||||
|
}
|
||||||
|
} catch (InterruptedIOException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Unable to open stream " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +209,22 @@ public class ChapterUtils {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static List<Chapter> readM4AChaptersFromInputStream(InputStream input) {
|
||||||
|
M4AChapterReader reader = new M4AChapterReader(new BufferedInputStream(input));
|
||||||
|
reader.readInputStream();
|
||||||
|
List<Chapter> chapters = reader.getChapters();
|
||||||
|
if (chapters == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
Collections.sort(chapters, new ChapterStartTimeComparator());
|
||||||
|
enumerateEmptyChapterTitles(chapters);
|
||||||
|
if (chaptersValid(chapters)) {
|
||||||
|
return chapters;
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes sure that chapter does a title and an item attribute.
|
* Makes sure that chapter does a title and an item attribute.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
<item name="android:splitMotionEvents">false</item>
|
<item name="android:splitMotionEvents">false</item>
|
||||||
<item name="android:fitsSystemWindows">false</item>
|
<item name="android:fitsSystemWindows">false</item>
|
||||||
<item name="android:windowContentTransitions">true</item>
|
<item name="android:windowContentTransitions">true</item>
|
||||||
|
<item name="toolbarStyle">@style/Style.AntennaPod.Toolbar</item>
|
||||||
<item name="preferenceTheme">@style/AppPreferenceThemeOverlay</item>
|
<item name="preferenceTheme">@style/AppPreferenceThemeOverlay</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -52,9 +53,9 @@
|
||||||
<style name="Theme.Base.AntennaPod.Dynamic.Dark" parent="Theme.Material3.DynamicColors.Dark">
|
<style name="Theme.Base.AntennaPod.Dynamic.Dark" parent="Theme.Material3.DynamicColors.Dark">
|
||||||
<item name="progressBarTheme">@style/ProgressBarDark</item>
|
<item name="progressBarTheme">@style/ProgressBarDark</item>
|
||||||
<item name="background_color">@color/background_darktheme</item>
|
<item name="background_color">@color/background_darktheme</item>
|
||||||
<item name="actionBarStyle">@style/Widget.AntennaPod.ActionBar</item>
|
|
||||||
<item name="background_elevated">@color/background_elevated_darktheme</item>
|
<item name="background_elevated">@color/background_elevated_darktheme</item>
|
||||||
<item name="action_icon_color">@color/white</item>
|
<item name="action_icon_color">@color/white</item>
|
||||||
|
<item name="actionBarStyle">@style/Widget.AntennaPod.ActionBar</item>
|
||||||
<item name="android:textAllCaps">false</item>
|
<item name="android:textAllCaps">false</item>
|
||||||
<item name="seek_background">@color/seek_background_dark</item>
|
<item name="seek_background">@color/seek_background_dark</item>
|
||||||
<item name="dragview_background">@drawable/ic_drag_darktheme</item>
|
<item name="dragview_background">@drawable/ic_drag_darktheme</item>
|
||||||
|
@ -70,6 +71,7 @@
|
||||||
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
|
<item name="android:windowLightStatusBar" tools:targetApi="m">false</item>
|
||||||
<item name="android:windowContentTransitions">true</item>
|
<item name="android:windowContentTransitions">true</item>
|
||||||
<item name="android:navigationBarColor">@color/background_darktheme</item>
|
<item name="android:navigationBarColor">@color/background_darktheme</item>
|
||||||
|
<item name="toolbarStyle">@style/Style.AntennaPod.Toolbar</item>
|
||||||
<item name="preferenceTheme">@style/AppPreferenceThemeOverlay</item>
|
<item name="preferenceTheme">@style/AppPreferenceThemeOverlay</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -227,6 +229,15 @@
|
||||||
<item name="windowSplashScreenAnimatedIcon">@drawable/launcher_animate</item>
|
<item name="windowSplashScreenAnimatedIcon">@drawable/launcher_animate</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Style.AntennaPod.Toolbar" parent="Widget.Material3.Toolbar">
|
||||||
|
<item name="materialThemeOverlay">@style/Theme.AntennaPod.Toolbar</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.AntennaPod.Toolbar" parent="ThemeOverlay.MaterialComponents.Toolbar.Surface">
|
||||||
|
<item name="action_icon_color">?attr/colorOnSurface</item>
|
||||||
|
<item name="colorControlNormal">?attr/colorOnSurface</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="Theme.AntennaPod.VideoPlayer" parent="@style/Theme.AntennaPod.Dark">
|
<style name="Theme.AntennaPod.VideoPlayer" parent="@style/Theme.AntennaPod.Dark">
|
||||||
<item name="windowActionBarOverlay">true</item>
|
<item name="windowActionBarOverlay">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
|
||||||
app:title="@string/discover"
|
app:title="@string/discover"
|
||||||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||||
app:navigationIcon="?homeAsUpIndicator" />
|
app:navigationIcon="?homeAsUpIndicator" />
|
||||||
|
|
Loading…
Reference in New Issue