Merge pull request #3813 from ByteHamster/shownotes-webview
ShownotesWebView improvements
This commit is contained in:
commit
63290ae762
|
@ -55,7 +55,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", 11 * 60 * 1000);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStr + " here.</p>", 2 * 60 * 1000);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 2 * 60 * 60 * 1000);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000 }, timeStrings);
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode " + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 3 * 60 * 60 * 1000);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 2 * 60 * 1000 + 12 * 1000 }, timeStrings);
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode (" + timeStr + ") here.</p>", Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode [" + timeStr + "] here.</p>", Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, "<p> Some test text with a timecode <" + timeStr + "> here.</p>", Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
|
||||
}
|
||||
|
||||
|
@ -181,7 +181,7 @@ public class TimelineTest {
|
|||
|
||||
Playable p = newTestPlayable(null, shownotes.toString(), Integer.MAX_VALUE);
|
||||
Timeline t = new Timeline(context, p);
|
||||
String res = t.processShownotes(true);
|
||||
String res = t.processShownotes();
|
||||
checkLinkCorrect(res, new long[0], new String[0]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,40 +1,18 @@
|
|||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Toast;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MediaplayerInfoActivity;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
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 androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.core.util.playback.Timeline;
|
||||
import de.danoeh.antennapod.view.ShownotesWebView;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
@ -44,72 +22,29 @@ import io.reactivex.schedulers.Schedulers;
|
|||
* Displays the description of a Playable object in a Webview.
|
||||
*/
|
||||
public class ItemDescriptionFragment extends Fragment {
|
||||
|
||||
private static final String TAG = "ItemDescriptionFragment";
|
||||
|
||||
private static final String PREF = "ItemDescriptionFragmentPrefs";
|
||||
private static final String PREF_SCROLL_Y = "prefScrollY";
|
||||
private static final String PREF_PLAYABLE_ID = "prefPlayableId";
|
||||
|
||||
private WebView webvDescription;
|
||||
private ShownotesWebView webvDescription;
|
||||
private Disposable webViewLoader;
|
||||
private PlaybackController controller;
|
||||
|
||||
/**
|
||||
* URL that was selected via long-press.
|
||||
*/
|
||||
private String selectedURL;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
Log.d(TAG, "Creating view");
|
||||
webvDescription = new WebView(getActivity().getApplicationContext());
|
||||
webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
|
||||
TypedArray ta = getActivity().getTheme().obtainStyledAttributes(new int[]
|
||||
{android.R.attr.colorBackground});
|
||||
boolean black = UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark
|
||||
|| UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack;
|
||||
int backgroundColor = ta.getColor(0, black ? Color.BLACK : Color.WHITE);
|
||||
|
||||
ta.recycle();
|
||||
webvDescription.setBackgroundColor(backgroundColor);
|
||||
if (!NetworkUtils.networkAvailable()) {
|
||||
webvDescription.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
|
||||
// Use cached resources, even if they have expired
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
webvDescription.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
}
|
||||
|
||||
webvDescription.getSettings().setUseWideViewPort(false);
|
||||
webvDescription.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
|
||||
webvDescription.getSettings().setLoadWithOverviewMode(true);
|
||||
webvDescription.setOnLongClickListener(webViewLongClickListener);
|
||||
webvDescription.setWebViewClient(new WebViewClient() {
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (Timeline.isTimecodeLink(url)) {
|
||||
onTimecodeLinkSelected(url);
|
||||
} else {
|
||||
IntentUtils.openInBrowser(getContext(), url);
|
||||
}
|
||||
return true;
|
||||
webvDescription = new ShownotesWebView(getActivity().getApplicationContext());
|
||||
webvDescription.setTimecodeSelectedListener(time -> {
|
||||
if (controller != null) {
|
||||
controller.seekTo(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
Log.d(TAG, "Page finished");
|
||||
// Restoring the scroll position might not always work
|
||||
view.postDelayed(ItemDescriptionFragment.this::restoreFromPreference, 50);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
webvDescription.setPageFinishedListener(() -> {
|
||||
// Restoring the scroll position might not always work
|
||||
webvDescription.postDelayed(ItemDescriptionFragment.this::restoreFromPreference, 50);
|
||||
});
|
||||
registerForContextMenu(webvDescription);
|
||||
return webvDescription;
|
||||
}
|
||||
|
@ -127,91 +62,14 @@ public class ItemDescriptionFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
private final View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
WebView.HitTestResult r = webvDescription.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();
|
||||
webvDescription.showContextMenu();
|
||||
return true;
|
||||
}
|
||||
selectedURL = null;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
boolean handled = selectedURL != null;
|
||||
if (selectedURL != null) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.open_in_browser_item:
|
||||
IntentUtils.openInBrowser(getContext(), selectedURL);
|
||||
break;
|
||||
case R.id.share_url_item:
|
||||
ShareUtils.shareLink(getActivity(), selectedURL);
|
||||
break;
|
||||
case R.id.copy_url_item:
|
||||
ClipData clipData = ClipData.newPlainText(selectedURL,
|
||||
selectedURL);
|
||||
android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity()
|
||||
.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setPrimaryClip(clipData);
|
||||
Toast t = Toast.makeText(getActivity(),
|
||||
R.string.copied_url_msg, Toast.LENGTH_SHORT);
|
||||
t.show();
|
||||
break;
|
||||
case R.id.go_to_position_item:
|
||||
if (Timeline.isTimecodeLink(selectedURL)) {
|
||||
onTimecodeLinkSelected(selectedURL);
|
||||
} else {
|
||||
Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: " + selectedURL);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
|
||||
}
|
||||
selectedURL = null;
|
||||
}
|
||||
return handled;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
if (selectedURL != null) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
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(getActivity(), 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);
|
||||
}
|
||||
}
|
||||
return webvDescription.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
private void load() {
|
||||
Log.d(TAG, "load()");
|
||||
if(webViewLoader != null) {
|
||||
if (webViewLoader != null) {
|
||||
webViewLoader.dispose();
|
||||
}
|
||||
webViewLoader = Observable.fromCallable(this::loadData)
|
||||
|
@ -227,7 +85,7 @@ public class ItemDescriptionFragment extends Fragment {
|
|||
@NonNull
|
||||
private String loadData() {
|
||||
Timeline timeline = new Timeline(getActivity(), controller.getMedia());
|
||||
return timeline.processShownotes(true);
|
||||
return timeline.processShownotes();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -238,8 +96,7 @@ public class ItemDescriptionFragment extends Fragment {
|
|||
|
||||
private void savePreference() {
|
||||
Log.d(TAG, "Saving preferences");
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREF,
|
||||
Activity.MODE_PRIVATE);
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
if (controller != null && controller.getMedia() != null && webvDescription != null) {
|
||||
Log.d(TAG, "Saving scroll position: " + webvDescription.getScrollY());
|
||||
|
@ -251,15 +108,14 @@ public class ItemDescriptionFragment extends Fragment {
|
|||
editor.putInt(PREF_SCROLL_Y, -1);
|
||||
editor.putString(PREF_PLAYABLE_ID, "");
|
||||
}
|
||||
editor.commit();
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private boolean restoreFromPreference() {
|
||||
Log.d(TAG, "Restoring from preferences");
|
||||
Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
SharedPreferences prefs = activity.getSharedPreferences(
|
||||
PREF, Activity.MODE_PRIVATE);
|
||||
SharedPreferences prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE);
|
||||
String id = prefs.getString(PREF_PLAYABLE_ID, "");
|
||||
int scrollY = prefs.getInt(PREF_SCROLL_Y, -1);
|
||||
if (controller != null && scrollY != -1 && controller.getMedia() != null
|
||||
|
@ -274,16 +130,6 @@ public class ItemDescriptionFragment extends Fragment {
|
|||
return false;
|
||||
}
|
||||
|
||||
private void onTimecodeLinkSelected(String link) {
|
||||
int time = Timeline.getTimecodeLinkTime(link);
|
||||
if (getActivity() != null && getActivity() instanceof MediaplayerInfoActivity) {
|
||||
PlaybackController pc = ((MediaplayerInfoActivity) getActivity()).getPlaybackController();
|
||||
if (pc != null) {
|
||||
pc.seekTo(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -11,31 +9,22 @@ import android.text.Layout;
|
|||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.joanzapata.iconify.Iconify;
|
||||
import com.joanzapata.iconify.widget.IconButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton;
|
||||
|
@ -47,7 +36,6 @@ import de.danoeh.antennapod.core.feed.FeedItem;
|
|||
import de.danoeh.antennapod.core.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
|
||||
import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.service.download.Downloader;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.core.storage.DBTasks;
|
||||
|
@ -55,10 +43,9 @@ import de.danoeh.antennapod.core.storage.DBWriter;
|
|||
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.IntentUtils;
|
||||
import de.danoeh.antennapod.core.util.NetworkUtils;
|
||||
import de.danoeh.antennapod.core.util.ShareUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.core.util.playback.Timeline;
|
||||
import de.danoeh.antennapod.view.ShownotesWebView;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
|
@ -99,7 +86,7 @@ public class ItemFragment extends Fragment {
|
|||
private List<Downloader> downloaderList;
|
||||
|
||||
private ViewGroup root;
|
||||
private WebView webvDescription;
|
||||
private ShownotesWebView webvDescription;
|
||||
private TextView txtvPodcast;
|
||||
private TextView txtvTitle;
|
||||
private TextView txtvDuration;
|
||||
|
@ -111,11 +98,7 @@ public class ItemFragment extends Fragment {
|
|||
private Button butAction2;
|
||||
|
||||
private Disposable disposable;
|
||||
|
||||
/**
|
||||
* URL that was selected via long-press.
|
||||
*/
|
||||
private String selectedURL;
|
||||
private PlaybackController controller;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -134,7 +117,7 @@ public class ItemFragment extends Fragment {
|
|||
txtvPodcast = layout.findViewById(R.id.txtvPodcast);
|
||||
txtvPodcast.setOnClickListener(v -> openPodcast());
|
||||
txtvTitle = layout.findViewById(R.id.txtvTitle);
|
||||
if(Build.VERSION.SDK_INT >= 23) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
txtvTitle.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
|
||||
}
|
||||
txtvDuration = layout.findViewById(R.id.txtvDuration);
|
||||
|
@ -143,31 +126,11 @@ public class ItemFragment extends Fragment {
|
|||
txtvTitle.setEllipsize(TextUtils.TruncateAt.END);
|
||||
}
|
||||
webvDescription = layout.findViewById(R.id.webvDescription);
|
||||
if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark ||
|
||||
UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack) {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
}
|
||||
webvDescription.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.black));
|
||||
}
|
||||
if (!NetworkUtils.networkAvailable()) {
|
||||
webvDescription.getSettings().setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
|
||||
// Use cached resources, even if they have expired
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
webvDescription.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
||||
}
|
||||
webvDescription.getSettings().setUseWideViewPort(false);
|
||||
webvDescription.getSettings().setLayoutAlgorithm(
|
||||
WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
|
||||
webvDescription.getSettings().setLoadWithOverviewMode(true);
|
||||
webvDescription.setOnLongClickListener(webViewLongClickListener);
|
||||
|
||||
webvDescription.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
IntentUtils.openInBrowser(getContext(), url);
|
||||
return true;
|
||||
webvDescription.setTimecodeSelectedListener(time -> {
|
||||
if (controller != null && item.getMedia().getIdentifier().equals(controller.getMedia().getIdentifier())) {
|
||||
controller.seekTo(time);
|
||||
} else {
|
||||
Snackbar.make(getView(), R.string.play_this_to_seek_position, Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
registerForContextMenu(webvDescription);
|
||||
|
@ -230,6 +193,8 @@ public class ItemFragment extends Fragment {
|
|||
public void onStart() {
|
||||
super.onStart();
|
||||
EventBus.getDefault().register(this);
|
||||
controller = new PlaybackController(getActivity(), false);
|
||||
controller.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -245,12 +210,13 @@ public class ItemFragment extends Fragment {
|
|||
public void onStop() {
|
||||
super.onStop();
|
||||
EventBus.getDefault().unregister(this);
|
||||
controller.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
if(disposable != null) {
|
||||
if (disposable != null) {
|
||||
disposable.dispose();
|
||||
}
|
||||
if (webvDescription != null && root != null) {
|
||||
|
@ -364,76 +330,14 @@ public class ItemFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
private final View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
WebView.HitTestResult r = webvDescription.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();
|
||||
webvDescription.showContextMenu();
|
||||
return true;
|
||||
}
|
||||
selectedURL = null;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
boolean handled = selectedURL != null;
|
||||
if (selectedURL != null) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.open_in_browser_item:
|
||||
IntentUtils.openInBrowser(getContext(), selectedURL);
|
||||
break;
|
||||
case R.id.share_url_item:
|
||||
ShareUtils.shareLink(getActivity(), selectedURL);
|
||||
break;
|
||||
case R.id.copy_url_item:
|
||||
ClipData clipData = ClipData.newPlainText(selectedURL,
|
||||
selectedURL);
|
||||
android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity()
|
||||
.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setPrimaryClip(clipData);
|
||||
Toast t = Toast.makeText(getActivity(),
|
||||
R.string.copied_url_msg, Toast.LENGTH_SHORT);
|
||||
t.show();
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
|
||||
}
|
||||
selectedURL = null;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenu.ContextMenuInfo menuInfo) {
|
||||
if (selectedURL != null) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
Uri uri = Uri.parse(selectedURL);
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
if(IntentUtils.isCallable(getActivity(), 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);
|
||||
}
|
||||
return webvDescription.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
private void openPodcast() {
|
||||
Fragment fragment = FeedItemlistFragment.newInstance(item.getFeedId());
|
||||
((MainActivity)getActivity()).loadChildFragment(fragment);
|
||||
((MainActivity) getActivity()).loadChildFragment(fragment);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
|
@ -452,11 +356,11 @@ public class ItemFragment extends Fragment {
|
|||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
||||
DownloaderUpdate update = event.update;
|
||||
downloaderList = update.downloaders;
|
||||
if(item == null || item.getMedia() == null) {
|
||||
if (item == null || item.getMedia() == null) {
|
||||
return;
|
||||
}
|
||||
long mediaId = item.getMedia().getId();
|
||||
if(ArrayUtils.contains(update.mediaIds, mediaId)) {
|
||||
if (ArrayUtils.contains(update.mediaIds, mediaId)) {
|
||||
if (itemsLoaded && getActivity() != null) {
|
||||
updateAppearance();
|
||||
}
|
||||
|
@ -490,7 +394,7 @@ public class ItemFragment extends Fragment {
|
|||
Context context = getContext();
|
||||
if (feedItem != null && context != null) {
|
||||
Timeline t = new Timeline(context, feedItem);
|
||||
webviewData = t.processShownotes(false);
|
||||
webviewData = t.processShownotes();
|
||||
}
|
||||
return feedItem;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -158,7 +158,7 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<WebView
|
||||
<de.danoeh.antennapod.view.ShownotesWebView
|
||||
android:id="@+id/webvDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_below="@id/header"
|
||||
|
|
|
@ -106,11 +106,10 @@ public class Timeline {
|
|||
* This method does NOT change the original shownotes string of the shownotesProvider object and it should
|
||||
* also not be changed by the caller.
|
||||
*
|
||||
* @param addTimecodes True if this method should add timecode links
|
||||
* @return The processed HTML string.
|
||||
*/
|
||||
@NonNull
|
||||
public String processShownotes(final boolean addTimecodes) {
|
||||
public String processShownotes() {
|
||||
final Playable playable = (shownotesProvider instanceof Playable) ? (Playable) shownotesProvider : null;
|
||||
|
||||
// load shownotes
|
||||
|
@ -155,10 +154,7 @@ public class Timeline {
|
|||
document.head().appendElement("style").attr("type", "text/css").text(styleStr);
|
||||
|
||||
// apply timecode links
|
||||
if (addTimecodes) {
|
||||
addTimecodes(document, playable);
|
||||
}
|
||||
|
||||
addTimecodes(document, playable);
|
||||
return document.toString();
|
||||
}
|
||||
|
||||
|
@ -188,11 +184,6 @@ public class Timeline {
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
public void setShownotesProvider(@NonNull ShownotesProvider shownotesProvider) {
|
||||
this.shownotesProvider = shownotesProvider;
|
||||
}
|
||||
|
||||
private void addTimecodes(Document document, final Playable playable) {
|
||||
Elements elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX);
|
||||
Log.d(TAG, "Recognized " + elementsWithTimeCodes.size() + " timecodes");
|
||||
|
|
|
@ -192,6 +192,7 @@
|
|||
<string name="marked_as_read_label">Marked as played</string>
|
||||
<string name="mark_read_no_media_label">Mark as read</string>
|
||||
<string name="marked_as_read_no_media_label">Marked as read</string>
|
||||
<string name="play_this_to_seek_position">To jump to positions, you need to play the episode</string>
|
||||
<plurals name="marked_read_batch_label">
|
||||
<item quantity="one">%d episode marked as played.</item>
|
||||
<item quantity="other">%d episodes marked as played.</item>
|
||||
|
|
Loading…
Reference in New Issue