diff options
17 files changed, 190 insertions, 22 deletions
diff --git a/app/build.gradle b/app/build.gradle index db1e9dcbb..f726373cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,8 @@ android { } catch (Exception ignore) { } buildConfigField "String", "COMMIT_HASH", ('"' + commit + '"') + buildConfigField "String", "PODCASTINDEX_API_KEY", '"JRJPPWC6ZA7DKKTSU2R3"' + buildConfigField "String", "PODCASTINDEX_API_SECRET", '"7$$67JtrfkSYtAncGBEaJp$v$Y9$ZJUzYVy8GuBm"' javaCompileOptions { annotationProcessorOptions { diff --git a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java index 8c66b6a4c..a0ad99e7d 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -187,7 +187,7 @@ public class VideoplayerActivity extends MediaplayerActivity { videoControlsHider.stop(); if (System.currentTimeMillis() - lastScreenTap < 300) { - if (event.getX() > v.getMeasuredWidth() / 2) { + if (event.getX() > v.getMeasuredWidth() / 2.0f) { onFastForward(); showSkipAnimation(true); } else { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java index e45533826..7b41652bd 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -94,7 +94,7 @@ public class PlaybackControlsDialog extends DialogFragment { barRightVolume.setEnabled(false); } - final CheckBox skipSilence = (CheckBox) dialog.findViewById(R.id.skipSilence); + final CheckBox skipSilence = dialog.findViewById(R.id.skipSilence); skipSilence.setChecked(UserPreferences.isSkipSilence()); if (!UserPreferences.useExoplayer()) { skipSilence.setEnabled(false); diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java index ef8ed335d..1fc7a77b2 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -45,9 +45,8 @@ public class VariableSpeedDialog extends DialogFragment { AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(R.string.no_playback_plugin_title); builder.setMessage(R.string.no_playback_plugin_or_sonic_msg); - builder.setPositiveButton(R.string.enable_sonic, (dialog, which) -> { - UserPreferences.enableSonic(); - }); + builder.setPositiveButton(R.string.enable_sonic, (dialog, which) -> + UserPreferences.enableSonic()); builder.setNeutralButton(R.string.close_label, null); builder.show(); } diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastIndexPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastIndexPodcastSearcher.java new file mode 100644 index 000000000..c8e5dc4ef --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastIndexPodcastSearcher.java @@ -0,0 +1,126 @@ +package de.danoeh.antennapod.discovery; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; + +import de.danoeh.antennapod.BuildConfig; +import de.danoeh.antennapod.core.ClientConfig; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +public class PodcastIndexPodcastSearcher implements PodcastSearcher { + private static final String PODCASTINDEX_API_URL = "https://api.podcastindex.org/api/1.0/search/byterm?q=%s"; + + public PodcastIndexPodcastSearcher() { + } + + @Override + public Single<List<PodcastSearchResult>> search(String query) { + return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> { + + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.clear(); + Date now = new Date(); + calendar.setTime(now); + long secondsSinceEpoch = calendar.getTimeInMillis() / 1000L; + String apiHeaderTime = String.valueOf(secondsSinceEpoch); + String data4Hash = BuildConfig.PODCASTINDEX_API_KEY + BuildConfig.PODCASTINDEX_API_SECRET + apiHeaderTime; + String hashString = sha1(data4Hash); + + String encodedQuery; + try { + encodedQuery = URLEncoder.encode(query, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // this won't ever be thrown + encodedQuery = query; + } + + String formattedUrl = String.format(PODCASTINDEX_API_URL, encodedQuery); + + OkHttpClient client = AntennapodHttpClient.getHttpClient(); + Request.Builder httpReq = new Request.Builder() + .addHeader("X-Auth-Date", apiHeaderTime) + .addHeader("X-Auth-Key", BuildConfig.PODCASTINDEX_API_KEY) + .addHeader("Authorization", hashString) + .addHeader("User-Agent", ClientConfig.USER_AGENT) + .url(formattedUrl); + List<PodcastSearchResult> podcasts = new ArrayList<>(); + try { + Response response = client.newCall(httpReq.build()).execute(); + + if (response.isSuccessful()) { + String resultString = response.body().string(); + JSONObject result = new JSONObject(resultString); + JSONArray j = result.getJSONArray("feeds"); + + for (int i = 0; i < j.length(); i++) { + JSONObject podcastJson = j.getJSONObject(i); + PodcastSearchResult podcast = PodcastSearchResult.fromPodcastIndex(podcastJson); + if (podcast.feedUrl != null) { + podcasts.add(podcast); + } + } + } else { + subscriber.onError(new IOException(response.toString())); + } + } catch (IOException | JSONException e) { + subscriber.onError(e); + } + subscriber.onSuccess(podcasts); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + @Override + public Single<String> lookupUrl(String url) { + return Single.just(url); + } + + @Override + public boolean urlNeedsLookup(String url) { + return false; + } + + @Override + public String getName() { + return "Podcastindex.org"; + } + + private static String sha1(String clearString) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); + messageDigest.update(clearString.getBytes("UTF-8")); + return toHex(messageDigest.digest()); + } catch (Exception ignored) { + ignored.printStackTrace(); + return null; + } + } + + private static String toHex(byte[] bytes) { + StringBuilder buffer = new StringBuilder(); + for (byte b : bytes) { + buffer.append(String.format(Locale.getDefault(), "%02x", b)); + } + return buffer.toString(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java index 0f0c864b1..bba438d1d 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java @@ -103,4 +103,12 @@ public class PodcastSearchResult { searchHit.getUrl(), searchHit.getAuthor()); } + + public static PodcastSearchResult fromPodcastIndex(JSONObject json) { + String title = json.optString("title", ""); + String imageUrl = json.optString("image", null); + String feedUrl = json.optString("url", null); + String author = json.optString("author", null); + return new PodcastSearchResult(title, imageUrl, feedUrl, author); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java index 3f738424b..ad574cab6 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java @@ -19,6 +19,7 @@ public class PodcastSearcherRegistry { searchProviders.add(new SearcherInfo(new ItunesPodcastSearcher(), 1.f)); searchProviders.add(new SearcherInfo(new FyydPodcastSearcher(), 1.f)); searchProviders.add(new SearcherInfo(new GpodnetPodcastSearcher(), 0.0f)); + searchProviders.add(new SearcherInfo(new PodcastIndexPodcastSearcher(), 0.0f)); } return searchProviders; } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java index 167daa08b..a4646ad64 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -26,6 +26,7 @@ import de.danoeh.antennapod.activity.OpmlImportActivity; import de.danoeh.antennapod.discovery.CombinedSearcher; import de.danoeh.antennapod.discovery.FyydPodcastSearcher; import de.danoeh.antennapod.discovery.ItunesPodcastSearcher; +import de.danoeh.antennapod.discovery.PodcastIndexPodcastSearcher; import de.danoeh.antennapod.fragment.gpodnet.GpodnetMainFragment; /** @@ -55,6 +56,8 @@ public class AddFeedFragment extends Fragment { -> activity.loadChildFragment(OnlineSearchFragment.newInstance(FyydPodcastSearcher.class))); root.findViewById(R.id.btn_search_gpodder).setOnClickListener(v -> activity.loadChildFragment(new GpodnetMainFragment())); + root.findViewById(R.id.btn_search_podcastindex).setOnClickListener(v + -> activity.loadChildFragment(OnlineSearchFragment.newInstance(PodcastIndexPodcastSearcher.class))); combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox); combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index 26216b375..283b4b466 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -335,10 +335,8 @@ public class QueueFragment extends Fragment { if (keepSortedNew) { SortOrder sortOrder = UserPreferences.getQueueKeepSortedOrder(); DBWriter.reorderQueue(sortOrder, true); - if (recyclerAdapter != null) { - recyclerAdapter.updateDragDropEnabled(); - } - } else if (recyclerAdapter != null) { + } + if (recyclerAdapter != null) { recyclerAdapter.updateDragDropEnabled(); } getActivity().invalidateOptionsMenu(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java index 064c4b3bc..0d6e79e84 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java @@ -168,7 +168,7 @@ public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat { private void buildEpisodeCleanupPreference() { final Resources res = getActivity().getResources(); - ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_EPISODE_CLEANUP); + ListPreference pref = findPreference(UserPreferences.PREF_EPISODE_CLEANUP); String[] values = res.getStringArray( R.array.episode_cleanup_values); String[] entries = new String[values.length]; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java index f07e59afe..1fa1fed58 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java @@ -109,7 +109,7 @@ public class PlaybackPreferencesFragment extends PreferenceFragmentCompat { private void buildSmartMarkAsPlayedPreference() { final Resources res = getActivity().getResources(); - ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_SMART_MARK_AS_PLAYED_SECS); + ListPreference pref = findPreference(UserPreferences.PREF_SMART_MARK_AS_PLAYED_SECS); String[] values = res.getStringArray(R.array.smart_mark_as_played_values); String[] entries = new String[values.length]; for (int x = 0; x < values.length; x++) { diff --git a/app/src/main/java/de/danoeh/antennapod/view/LockableBottomSheetBehavior.java b/app/src/main/java/de/danoeh/antennapod/view/LockableBottomSheetBehavior.java index 8e8d98fc9..1b96c7c4f 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/LockableBottomSheetBehavior.java +++ b/app/src/main/java/de/danoeh/antennapod/view/LockableBottomSheetBehavior.java @@ -47,11 +47,11 @@ public class LockableBottomSheetBehavior<V extends View> extends ViewPagerBottom @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, - View target, int nestedScrollAxes) { + View target, int axes, int type) { boolean handled = false; if (!isLocked) { - handled = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); + handled = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type); } return handled; @@ -59,16 +59,16 @@ public class LockableBottomSheetBehavior<V extends View> extends ViewPagerBottom @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, - int dx, int dy, int[] consumed) { + int dx, int dy, int[] consumed, int type) { if (!isLocked) { - super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); + super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type); } } @Override - public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) { + public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int type) { if (!isLocked) { - super.onStopNestedScroll(coordinatorLayout, child, target); + super.onStopNestedScroll(coordinatorLayout, child, target, type); } } diff --git a/app/src/main/res/layout/addfeed.xml b/app/src/main/res/layout/addfeed.xml index ff0a54bc1..b3d7aef72 100644 --- a/app/src/main/res/layout/addfeed.xml +++ b/app/src/main/res/layout/addfeed.xml @@ -143,6 +143,20 @@ android:text="@string/browse_gpoddernet_label"/> <TextView + android:id="@+id/btn_search_podcastindex" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:drawablePadding="8dp" + app:drawableStartCompat="?attr/action_search" + app:drawableLeftCompat="?attr/action_search" + android:paddingTop="8dp" + android:paddingBottom="8dp" + android:background="?android:attr/selectableItemBackground" + android:textColor="?android:attr/textColorPrimary" + android:clickable="true" + android:text="@string/search_podcastindex_label"/> + + <TextView android:id="@+id/btn_opml_import" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java b/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java index bf5b85c82..6d8450a18 100644 --- a/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java +++ b/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java @@ -290,10 +290,10 @@ public class CustomMRControllerDialog extends MediaRouteControllerDialog { View playbackControlLayout = View.inflate(getContext(), R.layout.media_router_controller, wrapper); - titleView = (TextView) playbackControlLayout.findViewById(R.id.mrc_control_title); - subtitleView = (TextView) playbackControlLayout.findViewById(R.id.mrc_control_subtitle); + titleView = playbackControlLayout.findViewById(R.id.mrc_control_title); + subtitleView = playbackControlLayout.findViewById(R.id.mrc_control_subtitle); playbackControlLayout.findViewById(R.id.mrc_control_title_container).setOnClickListener(onClickListener); - playPauseButton = (ImageButton) playbackControlLayout.findViewById(R.id.mrc_control_play_pause); + playPauseButton = playbackControlLayout.findViewById(R.id.mrc_control_play_pause); playPauseButton.setOnClickListener(v -> { PlaybackStateCompat state; if (mediaController != null && (state = mediaController.getPlaybackState()) != null) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 177a66e5b..00d4e2e90 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.core.service.playback; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; +import android.app.UiModeManager; import android.bluetooth.BluetoothA2dp; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -10,6 +11,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.media.AudioManager; import android.media.MediaPlayer; @@ -1184,6 +1186,20 @@ public class PlaybackService extends MediaBrowserServiceCompat { capabilities = capabilities | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; } + UiModeManager uiModeManager = (UiModeManager) getApplicationContext().getSystemService(Context.UI_MODE_SERVICE); + if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) { + sessionState.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( + CUSTOM_ACTION_REWIND, + getString(R.string.rewind_label), R.drawable.ic_notification_fast_rewind) + .build()); + sessionState.addCustomAction( + new PlaybackStateCompat.CustomAction.Builder( + CUSTOM_ACTION_FAST_FORWARD, + getString(R.string.fast_forward_label), R.drawable.ic_notification_fast_forward) + .build()); + } + sessionState.setActions(capabilities); flavorHelper.sessionStateAddActionForWear(sessionState, diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 850dec9b3..f33027f36 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -714,6 +714,7 @@ <!-- Add podcast fragment --> <string name="search_podcast_hint">Search podcast…</string> <string name="search_itunes_label">Search iTunes</string> + <string name="search_podcastindex_label">Search Podcastindex.org</string> <string name="search_fyyd_label">Search fyyd</string> <string name="advanced">Advanced</string> <string name="add_podcast_by_url">Add Podcast by RSS address</string> diff --git a/core/src/play/java/de/danoeh/antennapod/core/cast/CastButtonVisibilityManager.java b/core/src/play/java/de/danoeh/antennapod/core/cast/CastButtonVisibilityManager.java index 527a652e3..8d0e40116 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/cast/CastButtonVisibilityManager.java +++ b/core/src/play/java/de/danoeh/antennapod/core/cast/CastButtonVisibilityManager.java @@ -3,7 +3,7 @@ package de.danoeh.antennapod.core.cast; import android.util.Log; import android.view.Menu; import android.view.MenuItem; -import androidx.core.view.MenuItemCompat; + import de.danoeh.antennapod.core.R; public class CastButtonVisibilityManager { @@ -115,6 +115,6 @@ public class CastButtonVisibilityManager { Log.e(TAG, "setShowAsAction(), but cast button not inflated"); return; } - MenuItemCompat.setShowAsAction(item, connected ? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction); + item.setShowAsAction(connected ? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction); } } |