363 lines
16 KiB
Java
363 lines
16 KiB
Java
package de.danoeh.antennapod.ui.screen.feed;
|
|
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.ClipData;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.res.Configuration;
|
|
import android.graphics.LightingColorFilter;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.ImageView;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
import androidx.activity.result.ActivityResultLauncher;
|
|
import androidx.activity.result.contract.ActivityResultContracts;
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.appcompat.content.res.AppCompatResources;
|
|
import androidx.documentfile.provider.DocumentFile;
|
|
import androidx.fragment.app.Fragment;
|
|
import com.bumptech.glide.Glide;
|
|
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.dialog.MaterialAlertDialogBuilder;
|
|
import com.google.android.material.snackbar.Snackbar;
|
|
import de.danoeh.antennapod.R;
|
|
import de.danoeh.antennapod.activity.MainActivity;
|
|
import de.danoeh.antennapod.ui.TransitionEffect;
|
|
import de.danoeh.antennapod.storage.database.DBReader;
|
|
import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
|
|
import de.danoeh.antennapod.ui.common.IntentUtils;
|
|
import de.danoeh.antennapod.ui.share.ShareUtils;
|
|
import de.danoeh.antennapod.ui.cleaner.HtmlToPlainText;
|
|
import de.danoeh.antennapod.model.feed.Feed;
|
|
import de.danoeh.antennapod.model.feed.FeedFunding;
|
|
import de.danoeh.antennapod.ui.glide.FastBlurTransformation;
|
|
import de.danoeh.antennapod.ui.screen.feed.preferences.EditUrlSettingsDialog;
|
|
import de.danoeh.antennapod.ui.statistics.StatisticsFragment;
|
|
import de.danoeh.antennapod.ui.statistics.feed.FeedStatisticsFragment;
|
|
import io.reactivex.Completable;
|
|
import io.reactivex.Maybe;
|
|
import io.reactivex.MaybeOnSubscribe;
|
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
import io.reactivex.disposables.Disposable;
|
|
import io.reactivex.schedulers.Schedulers;
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
|
|
/**
|
|
* Displays information about a feed.
|
|
*/
|
|
public class FeedInfoFragment extends Fragment implements MaterialToolbar.OnMenuItemClickListener {
|
|
|
|
private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId";
|
|
private static final String TAG = "FeedInfoActivity";
|
|
private final ActivityResultLauncher<Uri> addLocalFolderLauncher =
|
|
registerForActivityResult(new AddLocalFolder(), this::addLocalFolderResult);
|
|
|
|
private Feed feed;
|
|
private Disposable disposable;
|
|
private ImageView imgvCover;
|
|
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) {
|
|
FeedInfoFragment fragment = new FeedInfoFragment();
|
|
Bundle arguments = new Bundle();
|
|
arguments.putLong(EXTRA_FEED_ID, feed.getId());
|
|
fragment.setArguments(arguments);
|
|
return fragment;
|
|
}
|
|
|
|
private final View.OnClickListener copyUrlToClipboard = new View.OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
if (feed != null && feed.getDownloadUrl() != null) {
|
|
String url = feed.getDownloadUrl();
|
|
ClipData clipData = ClipData.newPlainText(url, url);
|
|
android.content.ClipboardManager cm = (android.content.ClipboardManager) getContext()
|
|
.getSystemService(Context.CLIPBOARD_SERVICE);
|
|
cm.setPrimaryClip(clipData);
|
|
if (Build.VERSION.SDK_INT <= 32) {
|
|
((MainActivity) getActivity()).showSnackbarAbovePlayer(R.string.copied_to_clipboard,
|
|
Snackbar.LENGTH_SHORT);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
@Nullable
|
|
@Override
|
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
|
@Nullable Bundle savedInstanceState) {
|
|
View root = inflater.inflate(R.layout.feedinfo, null);
|
|
toolbar = root.findViewById(R.id.toolbar);
|
|
toolbar.setTitle("");
|
|
toolbar.inflateMenu(R.menu.feedinfo);
|
|
toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack());
|
|
toolbar.setOnMenuItemClickListener(this);
|
|
refreshToolbarState();
|
|
|
|
AppBarLayout appBar = root.findViewById(R.id.appBar);
|
|
CollapsingToolbarLayout collapsingToolbar = root.findViewById(R.id.collapsing_toolbar);
|
|
ToolbarIconTintManager iconTintManager = new ToolbarIconTintManager(getContext(), toolbar, collapsingToolbar) {
|
|
@Override
|
|
protected void doTint(Context themedContext) {
|
|
toolbar.getMenu().findItem(R.id.visit_website_item)
|
|
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_web));
|
|
toolbar.getMenu().findItem(R.id.share_item)
|
|
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_share));
|
|
}
|
|
};
|
|
iconTintManager.updateTint();
|
|
appBar.addOnOffsetChangedListener(iconTintManager);
|
|
|
|
imgvCover = root.findViewById(R.id.imgvCover);
|
|
txtvTitle = root.findViewById(R.id.txtvTitle);
|
|
txtvAuthorHeader = root.findViewById(R.id.txtvAuthor);
|
|
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
|
|
imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000));
|
|
|
|
txtvDescription = root.findViewById(R.id.txtvDescription);
|
|
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);
|
|
getParentFragmentManager().beginTransaction().replace(R.id.statisticsFragmentContainer,
|
|
FeedStatisticsFragment.newInstance(feedId, false), "feed_statistics_fragment")
|
|
.commitAllowingStateLoss();
|
|
|
|
root.findViewById(R.id.btnvOpenStatistics).setOnClickListener(view -> {
|
|
StatisticsFragment fragment = new StatisticsFragment();
|
|
((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE);
|
|
});
|
|
|
|
return root;
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
long feedId = getArguments().getLong(EXTRA_FEED_ID);
|
|
disposable = Maybe.create((MaybeOnSubscribe<Feed>) emitter -> {
|
|
Feed feed = DBReader.getFeed(feedId, false, 0, 0);
|
|
if (feed != null) {
|
|
emitter.onSuccess(feed);
|
|
} else {
|
|
emitter.onComplete();
|
|
}
|
|
})
|
|
.subscribeOn(Schedulers.io())
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
.subscribe(result -> {
|
|
feed = result;
|
|
showFeed();
|
|
}, error -> Log.d(TAG, Log.getStackTraceString(error)), () -> { });
|
|
}
|
|
|
|
@Override
|
|
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
|
super.onConfigurationChanged(newConfig);
|
|
if (header == null || infoContainer == null) {
|
|
return;
|
|
}
|
|
int horizontalSpacing = (int) getResources().getDimension(R.dimen.additional_horizontal_spacing);
|
|
header.setPadding(horizontalSpacing, header.getPaddingTop(), horizontalSpacing, header.getPaddingBottom());
|
|
infoContainer.setPadding(horizontalSpacing, infoContainer.getPaddingTop(),
|
|
horizontalSpacing, infoContainer.getPaddingBottom());
|
|
}
|
|
|
|
private void showFeed() {
|
|
Log.d(TAG, "Language is " + feed.getLanguage());
|
|
Log.d(TAG, "Author is " + feed.getAuthor());
|
|
Log.d(TAG, "URL is " + feed.getDownloadUrl());
|
|
Glide.with(this)
|
|
.load(feed.getImageUrl())
|
|
.apply(new RequestOptions()
|
|
.placeholder(R.color.light_gray)
|
|
.error(R.color.light_gray)
|
|
.fitCenter()
|
|
.dontAnimate())
|
|
.into(imgvCover);
|
|
Glide.with(this)
|
|
.load(feed.getImageUrl())
|
|
.apply(new RequestOptions()
|
|
.placeholder(R.color.image_readability_tint)
|
|
.error(R.color.image_readability_tint)
|
|
.transform(new FastBlurTransformation())
|
|
.dontAnimate())
|
|
.into(imgvBackground);
|
|
|
|
txtvTitle.setText(feed.getTitle());
|
|
txtvTitle.setMaxLines(6);
|
|
|
|
String description = HtmlToPlainText.getPlainText(feed.getDescription());
|
|
|
|
txtvDescription.setText(description);
|
|
|
|
if (!TextUtils.isEmpty(feed.getAuthor())) {
|
|
txtvAuthorHeader.setText(feed.getAuthor());
|
|
}
|
|
|
|
txtvUrl.setText(feed.getDownloadUrl());
|
|
txtvUrl.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0);
|
|
|
|
if (feed.getPaymentLinks() == null || feed.getPaymentLinks().size() == 0) {
|
|
lblSupport.setVisibility(View.GONE);
|
|
txtvFundingUrl.setVisibility(View.GONE);
|
|
} else {
|
|
lblSupport.setVisibility(View.VISIBLE);
|
|
ArrayList<FeedFunding> fundingList = feed.getPaymentLinks();
|
|
|
|
// Filter for duplicates, but keep items in the order that they have in the feed.
|
|
Iterator<FeedFunding> i = fundingList.iterator();
|
|
while (i.hasNext()) {
|
|
FeedFunding funding = i.next();
|
|
for (FeedFunding other : fundingList) {
|
|
if (TextUtils.equals(other.url, funding.url)) {
|
|
if (other.content != null && funding.content != null
|
|
&& other.content.length() > funding.content.length()) {
|
|
i.remove();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StringBuilder str = new StringBuilder();
|
|
for (FeedFunding funding : fundingList) {
|
|
str.append(funding.content.isEmpty()
|
|
? getContext().getResources().getString(R.string.support_podcast)
|
|
: funding.content).append(" ").append(funding.url);
|
|
str.append("\n");
|
|
}
|
|
str = new StringBuilder(StringUtils.trim(str.toString()));
|
|
txtvFundingUrl.setText(str.toString());
|
|
}
|
|
|
|
refreshToolbarState();
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
super.onDestroy();
|
|
if (disposable != null) {
|
|
disposable.dispose();
|
|
}
|
|
}
|
|
|
|
private void refreshToolbarState() {
|
|
toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(feed != null && feed.isLocalFeed());
|
|
toolbar.getMenu().findItem(R.id.share_item).setVisible(feed != null && !feed.isLocalFeed());
|
|
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()))));
|
|
toolbar.getMenu().findItem(R.id.edit_feed_url_item).setVisible(feed != null && !feed.isLocalFeed());
|
|
}
|
|
|
|
@Override
|
|
public boolean onMenuItemClick(MenuItem item) {
|
|
if (feed == null) {
|
|
((MainActivity) getActivity()).showSnackbarAbovePlayer(
|
|
R.string.please_wait_for_data, Toast.LENGTH_LONG);
|
|
return false;
|
|
}
|
|
if (item.getItemId() == R.id.visit_website_item) {
|
|
IntentUtils.openInBrowser(getContext(), feed.getLink());
|
|
} else if (item.getItemId() == R.id.share_item) {
|
|
ShareUtils.shareFeedLink(getContext(), feed);
|
|
} else if (item.getItemId() == R.id.reconnect_local_folder) {
|
|
MaterialAlertDialogBuilder alert = new MaterialAlertDialogBuilder(getContext());
|
|
alert.setMessage(R.string.reconnect_local_folder_warning);
|
|
alert.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
try {
|
|
addLocalFolderLauncher.launch(null);
|
|
} catch (ActivityNotFoundException e) {
|
|
Log.e(TAG, "No activity found. Should never happen...");
|
|
}
|
|
});
|
|
alert.setNegativeButton(android.R.string.cancel, null);
|
|
alert.show();
|
|
} else if (item.getItemId() == R.id.edit_feed_url_item) {
|
|
new EditUrlSettingsDialog(getActivity(), feed) {
|
|
@Override
|
|
protected void setUrl(String url) {
|
|
feed.setDownloadUrl(url);
|
|
txtvUrl.setText(feed.getDownloadUrl());
|
|
txtvUrl.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0);
|
|
}
|
|
}.show();
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void addLocalFolderResult(final Uri uri) {
|
|
if (uri == null) {
|
|
return;
|
|
}
|
|
reconnectLocalFolder(uri);
|
|
}
|
|
|
|
private void reconnectLocalFolder(Uri uri) {
|
|
if (feed == null) {
|
|
return;
|
|
}
|
|
|
|
Completable.fromAction(() -> {
|
|
getActivity().getContentResolver()
|
|
.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
DocumentFile documentFile = DocumentFile.fromTreeUri(getContext(), uri);
|
|
if (documentFile == null) {
|
|
throw new IllegalArgumentException("Unable to retrieve document tree");
|
|
}
|
|
feed.setDownloadUrl(Feed.PREFIX_LOCAL_FOLDER + uri.toString());
|
|
FeedDatabaseWriter.updateFeed(getContext(), feed, true);
|
|
})
|
|
.subscribeOn(Schedulers.io())
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
.subscribe(
|
|
() -> ((MainActivity) getActivity())
|
|
.showSnackbarAbovePlayer(android.R.string.ok, Snackbar.LENGTH_SHORT),
|
|
error -> ((MainActivity) getActivity())
|
|
.showSnackbarAbovePlayer(error.getLocalizedMessage(), Snackbar.LENGTH_LONG));
|
|
}
|
|
|
|
private static class AddLocalFolder extends ActivityResultContracts.OpenDocumentTree {
|
|
@NonNull
|
|
@Override
|
|
public Intent createIntent(@NonNull final Context context, @Nullable final Uri input) {
|
|
return super.createIntent(context, input)
|
|
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
}
|
|
}
|
|
}
|