diff --git a/.circleci/config.yml b/.circleci/config.yml index 6bac87a46..7446862c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 jobs: build: docker: - - image: circleci/android:api-26-alpha + - image: circleci/android:api-28 working_directory: ~/AntennaPod @@ -13,7 +13,7 @@ jobs: steps: - checkout - + - restore_cache: keys: - v1-android-{{ checksum "build.gradle" }} @@ -24,7 +24,7 @@ jobs: # To build release, we need to create a temporary keystore that can be used to sign the app command: | keytool -noprompt -genkey -v -keystore "app/keystore" -alias alias -storepass password -keypass password -keyalg RSA -validity 10 -dname "CN=antennapod.org, OU=dummy, O=dummy, L=dummy, S=dummy, C=US" - ./gradlew assemblePlayRelease :core:testPlayReleaseUnitTest -PdisablePreDex + ./gradlew assembleRelease :core:testPlayReleaseUnitTest -PdisablePreDex no_output_timeout: 1800 - store_artifacts: diff --git a/.gitignore b/.gitignore index 4f342ddca..7e39550e9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,6 @@ contributers.py proguard libs *.DS_Store -src/de/danoeh/antennapod/util/flattr/FlattrConfig.java gradle.properties *.keystore *.p12 diff --git a/app/build.gradle b/app/build.gradle index a9cce57d4..030dcfa4a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,16 +59,10 @@ android { buildTypes { def STRING = "String" - def FLATTR_APP_KEY = "FLATTR_APP_KEY" - def FLATTR_APP_SECRET = "FLATTR_APP_SECRET" - def mFlattrAppKey = (project.hasProperty("flattrAppKey")) ? flattrAppKey : "\"\"" - def mFlattrAppSecret = (project.hasProperty("flattrAppSecret")) ? flattrAppSecret : "\"\"" debug { applicationIdSuffix ".debug" resValue "string", "provider_authority", "de.danoeh.antennapod.debug.provider" - buildConfigField STRING, FLATTR_APP_KEY, mFlattrAppKey - buildConfigField STRING, FLATTR_APP_SECRET, mFlattrAppSecret dexcount { if (project.hasProperty("enableDexcountInDebug")) { runOnEachPackage enableDexcountInDebug.toBoolean() @@ -82,8 +76,6 @@ android { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), "proguard.cfg" signingConfig signingConfigs.releaseConfig - buildConfigField STRING, FLATTR_APP_KEY, mFlattrAppKey - buildConfigField STRING, FLATTR_APP_SECRET, mFlattrAppSecret } } @@ -145,9 +137,6 @@ dependencies { annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" implementation "org.apache.commons:commons-lang3:$commonslangVersion" - implementation("org.shredzone.flattr4j:flattr4j-core:$flattr4jVersion") { - exclude group: "org.json", module: "json" - } implementation "commons-io:commons-io:$commonsioVersion" implementation "org.jsoup:jsoup:$jsoupVersion" implementation "com.github.bumptech.glide:glide:$glideVersion" @@ -176,7 +165,7 @@ dependencies { implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion" implementation 'com.github.mfietz:fyydlin:v0.4.2' - implementation 'com.github.ByteHamster:SearchPreference:v1.2.5' + implementation 'com.github.ByteHamster:SearchPreference:v1.2.6' implementation "org.awaitility:awaitility:$awaitilityVersion" androidTestImplementation "com.jayway.android.robotium:robotium-solo:$robotiumSoloVersion" diff --git a/app/proguard.cfg b/app/proguard.cfg index 31f51ac4a..958048ef3 100644 --- a/app/proguard.cfg +++ b/app/proguard.cfg @@ -75,9 +75,6 @@ -dontwarn android.support.v7.** -dontwarn com.google.android.wearable.** --keep class org.shredzone.flattr4j.** { *; } --dontwarn org.shredzone.flattr4j.** - -keep class org.apache.commons.** { *; } -dontskipnonpubliclibraryclassmembers diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java index a577e5e36..f58008172 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java @@ -14,7 +14,6 @@ import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.SimpleChapter; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator; -import de.danoeh.antennapod.core.util.flattr.FlattrStatus; /** * Utility methods for DB* tests. @@ -46,7 +45,7 @@ class DBTestUtils { adapter.open(); for (int i = 0; i < numFeeds; i++) { Feed f = new Feed(0, null, "feed " + i, null, "link" + i, "descr", null, null, - null, null, "id" + i, null, null, "url" + i, false, new FlattrStatus(), false, null, null, false); + null, null, "id" + i, null, null, "url" + i, false, false, null, null, false); f.setItems(new ArrayList<>()); for (int j = 0; j < numItems; j++) { FeedItem item = new FeedItem(0, "item " + j, "id" + j, "link" + j, new Date(), diff --git a/app/src/androidTest/java/de/test/antennapod/util/syndication/feedgenerator/GeneratorUtil.java b/app/src/androidTest/java/de/test/antennapod/util/syndication/feedgenerator/GeneratorUtil.java index 89542d222..9aedbb493 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/syndication/feedgenerator/GeneratorUtil.java +++ b/app/src/androidTest/java/de/test/antennapod/util/syndication/feedgenerator/GeneratorUtil.java @@ -14,7 +14,6 @@ class GeneratorUtil { String ns = (withNamespace) ? "http://www.w3.org/2005/Atom" : null; xml.startTag(ns, "link"); xml.attribute(null, "rel", "payment"); - xml.attribute(null, "title", "Flattr this!"); xml.attribute(null, "href", paymentLink); xml.attribute(null, "type", "text/html"); xml.endTag(ns, "link"); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee1cf8936..73af654e9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + - - - - - - - - - - - - - - - + android:label="@string/gpodnet_auth_label"> diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java index bfa694e5c..26e360bd3 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java @@ -199,8 +199,6 @@ public class FeedInfoActivity extends AppCompatActivity { @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - menu.findItem(R.id.support_item).setVisible( - feed != null && feed.getPaymentLink() != null); menu.findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null); menu.findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null && IntentUtils.isCallable(this, new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink())))); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java deleted file mode 100644 index 2b4384a02..000000000 --- a/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java +++ /dev/null @@ -1,120 +0,0 @@ -package de.danoeh.antennapod.activity; - - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import org.shredzone.flattr4j.exception.FlattrException; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; - -/** Guides the user through the authentication process */ - -public class FlattrAuthActivity extends AppCompatActivity { - private static final String TAG = "FlattrAuthActivity"; - - private TextView txtvExplanation; - private Button butAuthenticate; - private Button butReturn; - - private boolean authSuccessful; - - private static FlattrAuthActivity singleton; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(UserPreferences.getTheme()); - super.onCreate(savedInstanceState); - singleton = this; - authSuccessful = false; - if (BuildConfig.DEBUG) Log.d(TAG, "Activity created"); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setContentView(R.layout.flattr_auth); - txtvExplanation = findViewById(R.id.txtvExplanation); - butAuthenticate = findViewById(R.id.but_authenticate); - butReturn = findViewById(R.id.but_return_home); - - butReturn.setOnClickListener(v -> { - Intent intent = new Intent(FlattrAuthActivity.this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - }); - - butAuthenticate.setOnClickListener(v -> { - try { - FlattrUtils.startAuthProcess(FlattrAuthActivity.this); - } catch (FlattrException e) { - e.printStackTrace(); - } - }); - } - - public static FlattrAuthActivity getInstance() { - return singleton; - } - - @Override - protected void onResume() { - super.onResume(); - if (BuildConfig.DEBUG) Log.d(TAG, "Activity resumed"); - Uri uri = getIntent().getData(); - if (uri != null) { - if (BuildConfig.DEBUG) Log.d(TAG, "Received uri"); - FlattrUtils.handleCallback(this, uri); - } - } - - public void handleAuthenticationSuccess() { - authSuccessful = true; - txtvExplanation.setText(R.string.flattr_auth_success); - butAuthenticate.setEnabled(false); - butReturn.setVisibility(View.VISIBLE); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - return true; - } - - - - @Override - protected void onPause() { - super.onPause(); - if (authSuccessful) { - finish(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - if (authSuccessful) { - Intent intent = new Intent(this, PreferenceActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - } else { - finish(); - } - break; - default: - return false; - } - return true; - } - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index 91e89d7c5..728019196 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -208,7 +208,6 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi checkFirstLaunch(); PreferenceUpgrader.checkUpgrades(this); - NotificationUtils.createChannels(this); } private void saveLastNavFragment(String tag) { @@ -579,17 +578,17 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi } Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); switch(item.getItemId()) { - case R.id.mark_all_seen_item: - ConfirmationDialog markAllSeenConfirmationDialog = new ConfirmationDialog(this, - R.string.mark_all_seen_label, - R.string.mark_all_seen_confirmation_msg) { + case R.id.remove_all_new_flags_item: + ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(this, + R.string.remove_all_new_flags_label, + R.string.remove_all_new_flags_confirmation_msg) { @Override public void onConfirmButtonPressed(DialogInterface dialog) { dialog.dismiss(); - DBWriter.markFeedSeen(feed.getId()); + DBWriter.removeFeedNewFlag(feed.getId()); } }; - markAllSeenConfirmationDialog.createNewDialog().show(); + removeAllNewFlagsConfirmationDialog.createNewDialog().show(); return true; case R.id.mark_all_read_item: ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(this, diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index 3d5c59a4a..3946400a4 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -330,11 +330,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements Playable media = controller.getMedia(); boolean isFeedMedia = media != null && (media instanceof FeedMedia); - menu.findItem(R.id.support_item).setVisible(isFeedMedia && media.getPaymentLink() != null && - ((FeedMedia) media).getItem() != null && - ((FeedMedia) media).getItem().getFlattrStatus().flattrable() - ); - boolean hasWebsiteLink = ( getWebsiteLinkWithFallback(media) != null ); menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink); @@ -603,11 +598,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements Uri uri = Uri.parse(getWebsiteLinkWithFallback(media)); startActivity(new Intent(Intent.ACTION_VIEW, uri)); break; - case R.id.support_item: - if (media instanceof FeedMedia) { - DBTasks.flattrItemIfLoggedIn(this, ((FeedMedia) media).getItem()); - } - break; case R.id.share_link_item: if (media instanceof FeedMedia) { ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem()); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java index 191481dd8..4fec1cfc5 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerInfoActivity.java @@ -46,7 +46,6 @@ import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.download.AutoUpdateManager; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.dialog.RenameFeedDialog; @@ -170,6 +169,7 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem pager.setCurrentItem(lastPosition); } + @Override protected void onStart() { super.onStart(); EventDistributor.getInstance().register(contentUpdate); @@ -339,8 +339,8 @@ public abstract class MediaplayerInfoActivity extends MediaplayerActivity implem } Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset()); switch(item.getItemId()) { - case R.id.mark_all_seen_item: - DBWriter.markFeedSeen(feed.getId()); + case R.id.remove_all_new_flags_item: + DBWriter.removeFeedNewFlag(feed.getId()); return true; case R.id.mark_all_read_item: DBWriter.markFeedRead(feed.getId()); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java index c13f713e0..7e0ae173f 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -15,7 +15,6 @@ import com.bytehamster.lib.preferencesearch.SearchPreferenceResultListener; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.fragment.preferences.AutoDownloadPreferencesFragment; -import de.danoeh.antennapod.fragment.preferences.FlattrPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.GpodderPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.IntegrationsPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.MainPreferencesFragment; @@ -64,8 +63,6 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe prefFragment = new AutoDownloadPreferencesFragment(); } else if (screen == R.xml.preferences_gpodder) { prefFragment = new GpodderPreferencesFragment(); - } else if (screen == R.xml.preferences_flattr) { - prefFragment = new FlattrPreferencesFragment(); } else if (screen == R.xml.preferences_playback) { prefFragment = new PlaybackPreferencesFragment(); } @@ -86,8 +83,6 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe return R.string.user_interface_label; case R.xml.preferences_integrations: return R.string.integrations_label; - case R.xml.preferences_flattr: - return R.string.flattr_label; case R.xml.preferences_gpodder: return R.string.gpodnet_main_label; default: diff --git a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java index df21f713d..2d7898d5b 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java @@ -104,10 +104,6 @@ public class GpodnetAuthenticationActivity extends AppCompatActivity { return super.onOptionsItemSelected(item); } - @Override - public void onConfigurationChanged(Configuration newConfig) { - } - private void setupLoginView(View view) { final EditText username = view.findViewById(R.id.etxtUsername); final EditText password = view.findViewById(R.id.etxtPassword); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java index 8866d987e..7aa9f8f21 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.adapter; -import android.content.Context; import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; @@ -280,7 +279,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter selectAndDismiss(storagePath)); holder.radioButton.setOnClickListener((View v) -> selectAndDismiss(storagePath)); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedDiscoverAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedDiscoverAdapter.java new file mode 100644 index 000000000..df7ec46e0 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedDiscoverAdapter.java @@ -0,0 +1,76 @@ +package de.danoeh.antennapod.adapter; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.discovery.PodcastSearchResult; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +public class FeedDiscoverAdapter extends BaseAdapter { + + private final WeakReference mainActivityRef; + private final List data = new ArrayList<>(); + + public FeedDiscoverAdapter(MainActivity mainActivity) { + this.mainActivityRef = new WeakReference<>(mainActivity); + } + + public void updateData(List newData) { + data.clear(); + data.addAll(newData); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return data.size(); + } + + @Override + public PodcastSearchResult getItem(int position) { + return data.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Holder holder; + + if (convertView == null) { + convertView = View.inflate(mainActivityRef.get(), R.layout.quick_feed_discovery_item, null); + holder = new Holder(); + holder.imageView = convertView.findViewById(R.id.discovery_cover); + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + + + final PodcastSearchResult podcast = getItem(position); + Glide.with(mainActivityRef.get()) + .load(podcast.imageUrl) + .apply(new RequestOptions() + .placeholder(R.color.light_gray) + .fitCenter() + .dontAnimate()) + .into(holder.imageView); + + return convertView; + } + + static class Holder { + ImageView imageView; + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java index 2cf17c85f..f5213e4ab 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/itunes/ItunesAdapter.java @@ -2,7 +2,6 @@ package de.danoeh.antennapod.adapter.itunes; import android.content.Context; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; @@ -13,18 +12,14 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.RequestOptions; -import de.danoeh.antennapod.core.glide.ApGlideSettings; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; +import de.danoeh.antennapod.discovery.PodcastSearchResult; import java.util.List; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.mfietz.fyydlin.SearchHit; -public class ItunesAdapter extends ArrayAdapter { +public class ItunesAdapter extends ArrayAdapter { /** * Related Context */ @@ -33,7 +28,7 @@ public class ItunesAdapter extends ArrayAdapter { /** * List holding the podcasts found in the search */ - private final List data; + private final List data; /** * Constructor. @@ -41,7 +36,7 @@ public class ItunesAdapter extends ArrayAdapter { * @param context Related context * @param objects Search result */ - public ItunesAdapter(Context context, List objects) { + public ItunesAdapter(Context context, List objects) { super(context, 0, objects); this.data = objects; this.context = context; @@ -51,7 +46,7 @@ public class ItunesAdapter extends ArrayAdapter { @Override public View getView(int position, View convertView, @NonNull ViewGroup parent) { //Current podcast - Podcast podcast = data.get(position); + PodcastSearchResult podcast = data.get(position); //ViewHolder PodcastViewHolder viewHolder; @@ -93,75 +88,6 @@ public class ItunesAdapter extends ArrayAdapter { return view; } - /** - * Represents an individual podcast on the iTunes Store. - */ - public static class Podcast { //TODO: Move this out eventually. Possibly to core.itunes.model - - /** - * The name of the podcast - */ - public final String title; - - /** - * URL of the podcast image - */ - @Nullable - public final String imageUrl; - /** - * URL of the podcast feed - */ - @Nullable - public final String feedUrl; - - - private Podcast(String title, @Nullable String imageUrl, @Nullable String feedUrl) { - this.title = title; - this.imageUrl = imageUrl; - this.feedUrl = feedUrl; - } - - /** - * Constructs a Podcast instance from a iTunes search result - * - * @param json object holding the podcast information - * @throws JSONException - */ - public static Podcast fromSearch(JSONObject json) { - String title = json.optString("collectionName", ""); - String imageUrl = json.optString("artworkUrl100", null); - String feedUrl = json.optString("feedUrl", null); - return new Podcast(title, imageUrl, feedUrl); - } - - public static Podcast fromSearch(SearchHit searchHit) { - return new Podcast(searchHit.getTitle(), searchHit.getThumbImageURL(), searchHit.getXmlUrl()); - } - - /** - * Constructs a Podcast instance from iTunes toplist entry - * - * @param json object holding the podcast information - * @throws JSONException - */ - public static Podcast fromToplist(JSONObject json) throws JSONException { - String title = json.getJSONObject("title").getString("label"); - String imageUrl = null; - JSONArray images = json.getJSONArray("im:image"); - for(int i=0; imageUrl == null && i < images.length(); i++) { - JSONObject image = images.getJSONObject(i); - String height = image.getJSONObject("attributes").getString("height"); - if(Integer.parseInt(height) >= 100) { - imageUrl = image.getString("label"); - } - } - String feedUrl = "https://itunes.apple.com/lookup?id=" + - json.getJSONObject("id").getJSONObject("attributes").getString("im:id"); - return new Podcast(title, imageUrl, feedUrl); - } - - } - /** * View holder object for the GridView */ diff --git a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java index 4138738f6..3dd7c350d 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -16,7 +16,6 @@ class ClientConfigurator { ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl(); ClientConfig.gpodnetCallbacks = new GpodnetCallbacksImpl(); ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl(); - ClientConfig.flattrCallbacks = new FlattrCallbacksImpl(); ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl(); ClientConfig.castCallbacks = new CastCallbackImpl(); } diff --git a/app/src/main/java/de/danoeh/antennapod/config/FlattrCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/FlattrCallbacksImpl.java deleted file mode 100644 index 3817db6de..000000000 --- a/app/src/main/java/de/danoeh/antennapod/config/FlattrCallbacksImpl.java +++ /dev/null @@ -1,53 +0,0 @@ -package de.danoeh.antennapod.config; - - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -import org.shredzone.flattr4j.oauth.AccessToken; - -import de.danoeh.antennapod.BuildConfig; -import de.danoeh.antennapod.activity.FlattrAuthActivity; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.FlattrCallbacks; - -public class FlattrCallbacksImpl implements FlattrCallbacks { - private static final String TAG = "FlattrCallbacksImpl"; - - @Override - public boolean flattrEnabled() { - return true; - } - - @Override - public Intent getFlattrAuthenticationActivityIntent(Context context) { - return new Intent(context, FlattrAuthActivity.class); - } - - @Override - public PendingIntent getFlattrFailedNotificationContentIntent(Context context) { - return PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); - } - - @Override - public String getFlattrAppKey() { - return BuildConfig.FLATTR_APP_KEY; - } - - @Override - public String getFlattrAppSecret() { - return BuildConfig.FLATTR_APP_SECRET; - } - - @Override - public void handleFlattrAuthenticationSuccess(AccessToken token) { - FlattrAuthActivity instance = FlattrAuthActivity.getInstance(); - if (instance != null) { - instance.handleAuthenticationSuccess(); - } else { - Log.e(TAG, "FlattrAuthActivity instance was null"); - } - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java deleted file mode 100644 index c28342374..000000000 --- a/app/src/main/java/de/danoeh/antennapod/dialog/AutoFlattrPreferenceDialog.java +++ /dev/null @@ -1,97 +0,0 @@ -package de.danoeh.antennapod.dialog; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.support.v7.app.AlertDialog; -import android.view.View; -import android.widget.CheckBox; -import android.widget.SeekBar; -import android.widget.TextView; - -import org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; - -/** - * Creates a new AlertDialog that displays preferences for auto-flattring to the user. - */ -public class AutoFlattrPreferenceDialog { - - private AutoFlattrPreferenceDialog() { - } - - public static void newAutoFlattrPreferenceDialog(final Activity activity, final AutoFlattrPreferenceDialogInterface callback) { - Validate.notNull(activity); - Validate.notNull(callback); - - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - - @SuppressLint("InflateParams") View view = activity.getLayoutInflater().inflate(R.layout.autoflattr_preference_dialog, null); - final CheckBox chkAutoFlattr = view.findViewById(R.id.chkAutoFlattr); - final SeekBar skbPercent = view.findViewById(R.id.skbPercent); - final TextView txtvStatus = view.findViewById(R.id.txtvStatus); - - chkAutoFlattr.setChecked(UserPreferences.isAutoFlattr()); - skbPercent.setEnabled(chkAutoFlattr.isChecked()); - txtvStatus.setEnabled(chkAutoFlattr.isChecked()); - - final int initialValue = (int) (UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100.0f); - setStatusMsgText(activity, txtvStatus, initialValue); - skbPercent.setProgress(initialValue); - - chkAutoFlattr.setOnClickListener(v -> { - skbPercent.setEnabled(chkAutoFlattr.isChecked()); - txtvStatus.setEnabled(chkAutoFlattr.isChecked()); - }); - - skbPercent.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - setStatusMsgText(activity, txtvStatus, progress); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - - } - }); - - builder.setTitle(R.string.pref_auto_flattr_title) - .setView(view) - .setPositiveButton(R.string.confirm_label, (dialog, which) -> { - float progDouble = ((float) skbPercent.getProgress()) / 100.0f; - callback.onConfirmed(chkAutoFlattr.isChecked(), progDouble); - dialog.dismiss(); - }) - .setNegativeButton(R.string.cancel_label, (dialog, which) -> { - callback.onCancelled(); - dialog.dismiss(); - }) - .setCancelable(false).show(); - } - - private static void setStatusMsgText(Context context, TextView txtvStatus, int progress) { - if (progress == 0) { - txtvStatus.setText(R.string.auto_flattr_ater_beginning); - } else if (progress == 100) { - txtvStatus.setText(R.string.auto_flattr_ater_end); - } else { - txtvStatus.setText(context.getString(R.string.auto_flattr_after_percent, progress)); - } - } - - public interface AutoFlattrPreferenceDialogInterface { - void onCancelled(); - - void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue); - } - - -} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java index ab9b7fcf5..7697aaa69 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.dialog; import android.app.AlertDialog; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Bundle; import android.support.annotation.IdRes; import android.support.annotation.NonNull; @@ -12,6 +13,7 @@ import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.util.ArrayMap; +import android.support.v4.view.ViewCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.util.Log; @@ -45,7 +47,7 @@ public class EpisodesApplyActionFragment extends Fragment { public static final String TAG = "EpisodeActionFragment"; public static final int ACTION_ADD_TO_QUEUE = 1; - private static final int ACTION_REMOVE_FROM_QUEUE = 2; + public static final int ACTION_REMOVE_FROM_QUEUE = 2; private static final int ACTION_MARK_PLAYED = 4; private static final int ACTION_MARK_UNPLAYED = 8; private static final int ACTION_DOWNLOAD = 16; @@ -203,6 +205,10 @@ public class EpisodesApplyActionFragment extends Fragment { return true; }); + if (Build.VERSION.SDK_INT == 23 || Build.VERSION.SDK_INT == 24) { + ViewCompat.setElevation(view.findViewById(R.id.fabSDScrollCtr), 8); + } + showSpeedDialIfAnyChecked(); return view; @@ -218,10 +224,6 @@ public class EpisodesApplyActionFragment extends Fragment { mSpeedDialView.setVisibility(checkedIds.size() > 0 ? View.VISIBLE : View.GONE); } - private void hideSpeedDial() { - mSpeedDialView.setVisibility(View.GONE); - } - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/CombinedSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/CombinedSearcher.java new file mode 100644 index 000000000..18d65a03c --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/discovery/CombinedSearcher.java @@ -0,0 +1,96 @@ +package de.danoeh.antennapod.discovery; + +import android.content.Context; +import android.util.Log; +import android.util.Pair; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +public class CombinedSearcher implements PodcastSearcher { + private static final String TAG = "CombinedSearcher"; + + private final List> searchProviders = new ArrayList<>(); + + public CombinedSearcher(Context context) { + addProvider(new FyydPodcastSearcher(), 1.f); + addProvider(new ItunesPodcastSearcher(context), 1.f); + addProvider(new GpodnetPodcastSearcher(), 0.6f); + } + + private void addProvider(PodcastSearcher provider, float priority) { + searchProviders.add(new Pair<>(provider, priority)); + } + + public Single> search(String query) { + ArrayList disposables = new ArrayList<>(); + List> singleResults = new ArrayList<>(Collections.nCopies(searchProviders.size(), null)); + CountDownLatch latch = new CountDownLatch(searchProviders.size()); + for (int i = 0; i < searchProviders.size(); i++) { + Pair searchProviderInfo = searchProviders.get(i); + PodcastSearcher searcher = searchProviderInfo.first; + final int index = i; + disposables.add(searcher.search(query).subscribe(e -> { + singleResults.set(index, e); + latch.countDown(); + }, throwable -> { + Log.d(TAG, Log.getStackTraceString(throwable)); + latch.countDown(); + } + )); + } + + return Single.create((SingleOnSubscribe>) subscriber -> { + latch.await(); + List results = weightSearchResults(singleResults); + subscriber.onSuccess(results); + }) + .doOnDispose(() -> { + for (Disposable disposable : disposables) { + disposable.dispose(); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + private List weightSearchResults(List> singleResults) { + HashMap resultRanking = new HashMap<>(); + HashMap urlToResult = new HashMap<>(); + for (int i = 0; i < singleResults.size(); i++) { + float providerPriority = searchProviders.get(i).second; + List providerResults = singleResults.get(i); + if (providerResults == null) { + continue; + } + for (int position = 0; position < providerResults.size(); position++) { + PodcastSearchResult result = providerResults.get(position); + urlToResult.put(result.feedUrl, result); + + float ranking = 0; + if (resultRanking.containsKey(result.feedUrl)) { + ranking = resultRanking.get(result.feedUrl); + } + ranking += 1.f / (position + 1.f); + resultRanking.put(result.feedUrl, ranking * providerPriority); + } + } + List> sortedResults = new ArrayList<>(resultRanking.entrySet()); + Collections.sort(sortedResults, (o1, o2) -> Double.compare(o2.getValue(), o1.getValue())); + + List results = new ArrayList<>(); + for (Map.Entry res : sortedResults) { + results.add(urlToResult.get(res.getKey())); + } + return results; + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/FyydPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/FyydPodcastSearcher.java new file mode 100644 index 000000000..529a9e3d5 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/discovery/FyydPodcastSearcher.java @@ -0,0 +1,38 @@ +package de.danoeh.antennapod.discovery; + +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.mfietz.fyydlin.FyydClient; +import de.mfietz.fyydlin.FyydResponse; +import de.mfietz.fyydlin.SearchHit; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +import java.util.ArrayList; +import java.util.List; + +public class FyydPodcastSearcher implements PodcastSearcher { + private final FyydClient client = new FyydClient(AntennapodHttpClient.getHttpClient()); + + public Single> search(String query) { + return Single.create((SingleOnSubscribe>) subscriber -> { + FyydResponse response = client.searchPodcasts(query, 10) + .subscribeOn(Schedulers.io()) + .blockingGet(); + + ArrayList searchResults = new ArrayList<>(); + + if (!response.getData().isEmpty()) { + for (SearchHit searchHit : response.getData()) { + PodcastSearchResult podcast = PodcastSearchResult.fromFyyd(searchHit); + searchResults.add(podcast); + } + } + + subscriber.onSuccess(searchResults); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java new file mode 100644 index 000000000..6e5debb38 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java @@ -0,0 +1,38 @@ +package de.danoeh.antennapod.discovery; + +import de.danoeh.antennapod.core.gpoddernet.GpodnetService; +import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; +import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; +import io.reactivex.Single; +import io.reactivex.SingleOnSubscribe; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +import java.util.ArrayList; +import java.util.List; + +public class GpodnetPodcastSearcher implements PodcastSearcher { + public Single> search(String query) { + return Single.create((SingleOnSubscribe>) subscriber -> { + GpodnetService service = null; + try { + service = new GpodnetService(); + List gpodnetPodcasts = service.searchPodcasts(query, 0); + List results = new ArrayList<>(); + for (GpodnetPodcast podcast : gpodnetPodcasts) { + results.add(PodcastSearchResult.fromGpodder(podcast)); + } + subscriber.onSuccess(results); + } catch (GpodnetServiceException e) { + e.printStackTrace(); + subscriber.onError(e); + } finally { + if (service != null) { + service.shutdown(); + } + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java new file mode 100644 index 000000000..a91aae1a8 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java @@ -0,0 +1,74 @@ +package de.danoeh.antennapod.discovery; + +import android.content.Context; +import de.danoeh.antennapod.R; +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; +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.util.ArrayList; +import java.util.List; + +public class ItunesPodcastSearcher implements PodcastSearcher { + private static final String ITUNES_API_URL = "https://itunes.apple.com/search?media=podcast&term=%s"; + private final Context context; + + public ItunesPodcastSearcher(Context context) { + this.context = context; + } + + public Single> search(String query) { + return Single.create((SingleOnSubscribe>) subscriber -> { + String encodedQuery; + try { + encodedQuery = URLEncoder.encode(query, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // this won't ever be thrown + encodedQuery = query; + } + + String formattedUrl = String.format(ITUNES_API_URL, encodedQuery); + + OkHttpClient client = AntennapodHttpClient.getHttpClient(); + Request.Builder httpReq = new Request.Builder() + .url(formattedUrl) + .header("User-Agent", ClientConfig.USER_AGENT); + List 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("results"); + + for (int i = 0; i < j.length(); i++) { + JSONObject podcastJson = j.getJSONObject(i); + PodcastSearchResult podcast = PodcastSearchResult.fromItunes(podcastJson); + podcasts.add(podcast); + } + } else { + String prefix = context.getString(R.string.error_msg_prefix); + subscriber.onError(new IOException(prefix + response)); + } + } catch (IOException | JSONException e) { + subscriber.onError(e); + } + subscriber.onSuccess(podcasts); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesTopListLoader.java b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesTopListLoader.java new file mode 100644 index 000000000..bc9133258 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesTopListLoader.java @@ -0,0 +1,110 @@ +package de.danoeh.antennapod.discovery; + +import android.content.Context; +import de.danoeh.antennapod.R; +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; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class ItunesTopListLoader { + private final Context context; + + public ItunesTopListLoader(Context context) { + this.context = context; + } + + public Single> loadToplist(int limit) { + return Single.create((SingleOnSubscribe>) emitter -> { + String lang = Locale.getDefault().getLanguage(); + OkHttpClient client = AntennapodHttpClient.getHttpClient(); + String feedString; + try { + try { + feedString = getTopListFeed(client, lang, limit); + } catch (IOException e) { + feedString = getTopListFeed(client, "us", limit); + } + List podcasts = parseFeed(feedString); + emitter.onSuccess(podcasts); + } catch (IOException | JSONException e) { + emitter.onError(e); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + public Single getFeedUrl(PodcastSearchResult podcast) { + if (!podcast.feedUrl.contains("itunes.apple.com")) { + return Single.just(podcast.feedUrl) + .observeOn(AndroidSchedulers.mainThread()); + } + return Single.create((SingleOnSubscribe) emitter -> { + OkHttpClient client = AntennapodHttpClient.getHttpClient(); + Request.Builder httpReq = new Request.Builder() + .url(podcast.feedUrl) + .header("User-Agent", ClientConfig.USER_AGENT); + try { + Response response = client.newCall(httpReq.build()).execute(); + if (response.isSuccessful()) { + String resultString = response.body().string(); + JSONObject result = new JSONObject(resultString); + JSONObject results = result.getJSONArray("results").getJSONObject(0); + String feedUrl = results.getString("feedUrl"); + emitter.onSuccess(feedUrl); + } else { + String prefix = context.getString(R.string.error_msg_prefix); + emitter.onError(new IOException(prefix + response)); + } + } catch (IOException | JSONException e) { + emitter.onError(e); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + private String getTopListFeed(OkHttpClient client, String language, int limit) throws IOException { + String url = "https://itunes.apple.com/%s/rss/toppodcasts/limit="+limit+"/explicit=true/json"; + Request.Builder httpReq = new Request.Builder() + .header("User-Agent", ClientConfig.USER_AGENT) + .url(String.format(url, language)); + + try (Response response = client.newCall(httpReq.build()).execute()) { + if (response.isSuccessful()) { + return response.body().string(); + } + String prefix = context.getString(R.string.error_msg_prefix); + throw new IOException(prefix + response); + } + } + + private List parseFeed(String jsonString) throws JSONException { + JSONObject result = new JSONObject(jsonString); + JSONObject feed = result.getJSONObject("feed"); + JSONArray entries = feed.getJSONArray("entry"); + + List results = new ArrayList<>(); + for (int i=0; i < entries.length(); i++) { + JSONObject json = entries.getJSONObject(i); + results.add(PodcastSearchResult.fromItunesToplist(json)); + } + + return results; + } + +} diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java new file mode 100644 index 000000000..ca9ed83d7 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearchResult.java @@ -0,0 +1,81 @@ +package de.danoeh.antennapod.discovery; + +import android.support.annotation.Nullable; +import de.danoeh.antennapod.core.gpoddernet.model.GpodnetPodcast; +import de.mfietz.fyydlin.SearchHit; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class PodcastSearchResult { + + /** + * The name of the podcast + */ + public final String title; + + /** + * URL of the podcast image + */ + @Nullable + public final String imageUrl; + /** + * URL of the podcast feed + */ + @Nullable + public final String feedUrl; + + + private PodcastSearchResult(String title, @Nullable String imageUrl, @Nullable String feedUrl) { + this.title = title; + this.imageUrl = imageUrl; + this.feedUrl = feedUrl; + } + + public static PodcastSearchResult dummy() { + return new PodcastSearchResult("", "", ""); + } + + /** + * Constructs a Podcast instance from a iTunes search result + * + * @param json object holding the podcast information + * @throws JSONException + */ + public static PodcastSearchResult fromItunes(JSONObject json) { + String title = json.optString("collectionName", ""); + String imageUrl = json.optString("artworkUrl100", null); + String feedUrl = json.optString("feedUrl", null); + return new PodcastSearchResult(title, imageUrl, feedUrl); + } + + /** + * Constructs a Podcast instance from iTunes toplist entry + * + * @param json object holding the podcast information + * @throws JSONException + */ + public static PodcastSearchResult fromItunesToplist(JSONObject json) throws JSONException { + String title = json.getJSONObject("title").getString("label"); + String imageUrl = null; + JSONArray images = json.getJSONArray("im:image"); + for(int i=0; imageUrl == null && i < images.length(); i++) { + JSONObject image = images.getJSONObject(i); + String height = image.getJSONObject("attributes").getString("height"); + if(Integer.parseInt(height) >= 100) { + imageUrl = image.getString("label"); + } + } + String feedUrl = "https://itunes.apple.com/lookup?id=" + + json.getJSONObject("id").getJSONObject("attributes").getString("im:id"); + return new PodcastSearchResult(title, imageUrl, feedUrl); + } + + public static PodcastSearchResult fromFyyd(SearchHit searchHit) { + return new PodcastSearchResult(searchHit.getTitle(), searchHit.getThumbImageURL(), searchHit.getXmlUrl()); + } + + public static PodcastSearchResult fromGpodder(GpodnetPodcast searchHit) { + return new PodcastSearchResult(searchHit.getTitle(), searchHit.getLogoUrl(), searchHit.getUrl()); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java new file mode 100644 index 000000000..b19d7d695 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java @@ -0,0 +1,10 @@ +package de.danoeh.antennapod.discovery; + +import io.reactivex.Single; +import io.reactivex.disposables.Disposable; +import io.reactivex.functions.Consumer; +import java.util.List; + +public interface PodcastSearcher { + Single> search(String query); +} 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 ee2373da8..35bcaa76e 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -2,13 +2,20 @@ package de.danoeh.antennapod.fragment; import android.content.Intent; import android.os.Bundle; +import android.provider.MediaStore; import android.support.v4.app.Fragment; +import android.view.ContextMenu; +import android.view.KeyEvent; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; @@ -27,11 +34,28 @@ public class AddFeedFragment extends Fragment { */ private static final String ARG_FEED_URL = "feedurl"; + private EditText combinedFeedSearchBox; + private MainActivity activity; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View root = inflater.inflate(R.layout.addfeed, container, false); + activity = (MainActivity) getActivity(); + activity.getSupportActionBar().setTitle(R.string.add_feed_label); + + setupAdvancedSearchButtons(root); + setupSeachBox(root); + + View butOpmlImport = root.findViewById(R.id.btn_opml_import); + butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(), + OpmlImportFromPathActivity.class))); + + return root; + } + + private void setupSeachBox(View root) { final EditText etxtFeedurl = root.findViewById(R.id.etxtFeedurl); Bundle args = getArguments(); @@ -39,32 +63,69 @@ public class AddFeedFragment extends Fragment { etxtFeedurl.setText(args.getString(ARG_FEED_URL)); } - Button butSearchITunes = root.findViewById(R.id.butSearchItunes); - Button butBrowserGpoddernet = root.findViewById(R.id.butBrowseGpoddernet); - Button butSearchFyyd = root.findViewById(R.id.butSearchFyyd); - Button butOpmlImport = root.findViewById(R.id.butOpmlImport); - Button butConfirm = root.findViewById(R.id.butConfirm); - - final MainActivity activity = (MainActivity) getActivity(); - activity.getSupportActionBar().setTitle(R.string.add_feed_label); - - butSearchITunes.setOnClickListener(v -> activity.loadChildFragment(new ItunesSearchFragment())); - - butBrowserGpoddernet.setOnClickListener(v -> activity.loadChildFragment(new GpodnetMainFragment())); - - butSearchFyyd.setOnClickListener(v -> activity.loadChildFragment(new FyydSearchFragment())); - - butOpmlImport.setOnClickListener(v -> startActivity(new Intent(getActivity(), - OpmlImportFromPathActivity.class))); - - butConfirm.setOnClickListener(v -> { - Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); - intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, etxtFeedurl.getText().toString()); - intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label)); - startActivity(intent); + Button butConfirmAddUrl = root.findViewById(R.id.butConfirm); + butConfirmAddUrl.setOnClickListener(v -> { + addUrl(etxtFeedurl.getText().toString()); }); - return root; + combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox); + combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + performSearch(); + return true; + } + return false; + }); + } + + private void setupAdvancedSearchButtons(View root) { + View butAdvancedSearch = root.findViewById(R.id.advanced_search); + registerForContextMenu(butAdvancedSearch); + butAdvancedSearch.setOnClickListener(v -> butAdvancedSearch.showContextMenu()); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + getActivity().getMenuInflater().inflate(R.menu.advanced_search, menu); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.search_fyyd: + activity.loadChildFragment(new FyydSearchFragment()); + return true; + case R.id.search_gpodder: + activity.loadChildFragment(new GpodnetMainFragment()); + return true; + case R.id.search_itunes: + activity.loadChildFragment(new ItunesSearchFragment()); + return true; + } + return false; + } + + + private void addUrl(String url) { + Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); + intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, url); + intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label)); + startActivity(intent); + } + + private void performSearch() { + String query = combinedFeedSearchBox.getText().toString(); + + if (query.startsWith("http")) { + addUrl(query); + return; + } + + Bundle bundle = new Bundle(); + bundle.putString(CombinedSearchFragment.ARGUMENT_QUERY, query); + CombinedSearchFragment fragment = new CombinedSearchFragment(); + fragment.setArguments(bundle); + activity.loadChildFragment(fragment); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java index 8f4e9f656..62d798cf6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java @@ -21,11 +21,15 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; -import android.widget.TextView; import android.widget.Toast; import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.ArrayList; import java.util.List; import de.danoeh.antennapod.R; @@ -40,6 +44,7 @@ import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.download.DownloadRequest; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.service.download.Downloader; import de.danoeh.antennapod.core.storage.DBReader; @@ -50,15 +55,11 @@ import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; - import de.danoeh.antennapod.view.EmptyViewHandler; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; /** * Shows unread or recently published episodes @@ -81,11 +82,10 @@ public class AllEpisodesFragment extends Fragment { private ProgressBar progLoading; EmptyViewHandler emptyView; - List episodes; - private List downloaderList; - - private boolean itemsLoaded = false; - private boolean viewsCreated = false; + @NonNull + List episodes = new ArrayList<>(); + @NonNull + private List downloaderList = new ArrayList<>(); private boolean isUpdatingFeeds; boolean isMenuInvalidationAllowed = false; @@ -93,29 +93,26 @@ public class AllEpisodesFragment extends Fragment { Disposable disposable; private LinearLayoutManager layoutManager; - boolean showOnlyNewEpisodes() { return false; } - String getPrefName() { return DEFAULT_PREF_NAME; } + boolean showOnlyNewEpisodes() { + return false; + } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); + String getPrefName() { + return DEFAULT_PREF_NAME; } @Override public void onStart() { super.onStart(); + setHasOptionsMenu(true); EventDistributor.getInstance().register(contentUpdate); - if (viewsCreated && itemsLoaded) { - onFragmentLoaded(); - } EventBus.getDefault().register(this); + loadItems(); } @Override public void onResume() { super.onResume(); - loadItems(); registerForContextMenu(recyclerView); } @@ -136,17 +133,11 @@ public class AllEpisodesFragment extends Fragment { } } - @Override - public void onDestroyView() { - super.onDestroyView(); - resetViewState(); - } - private void saveScrollPosition() { int firstItem = layoutManager.findFirstVisibleItemPosition(); View firstItemView = layoutManager.findViewByPosition(firstItem); float topOffset; - if(firstItemView == null) { + if (firstItemView == null) { topOffset = 0; } else { topOffset = firstItemView.getTop(); @@ -173,43 +164,35 @@ public class AllEpisodesFragment extends Fragment { } } - void resetViewState() { - viewsCreated = false; - listAdapter = null; - } - - private final MenuItemUtils.UpdateRefreshMenuItemChecker updateRefreshMenuItemChecker = () -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if(!isAdded()) { + if (!isAdded()) { return; } super.onCreateOptionsMenu(menu, inflater); - if (itemsLoaded) { - inflater.inflate(R.menu.episodes, menu); + inflater.inflate(R.menu.episodes, menu); - MenuItem searchItem = menu.findItem(R.id.action_search); - final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); - sv.setQueryHint(getString(R.string.search_hint)); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - sv.clearFocus(); - ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s)); - return true; - } + MenuItem searchItem = menu.findItem(R.id.action_search); + final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); + MenuItemUtils.adjustTextColor(getActivity(), sv); + sv.setQueryHint(getString(R.string.search_hint)); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + ((MainActivity) requireActivity()).loadChildFragment(SearchFragment.newInstance(s)); + return true; + } - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker); - } + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker); } @Override @@ -217,11 +200,11 @@ public class AllEpisodesFragment extends Fragment { super.onPrepareOptionsMenu(menu); MenuItem markAllRead = menu.findItem(R.id.mark_all_read_item); if (markAllRead != null) { - markAllRead.setVisible(!showOnlyNewEpisodes() && episodes != null && !episodes.isEmpty()); + markAllRead.setVisible(!showOnlyNewEpisodes() && !episodes.isEmpty()); } - MenuItem markAllSeen = menu.findItem(R.id.mark_all_seen_item); - if(markAllSeen != null) { - markAllSeen.setVisible(showOnlyNewEpisodes() && episodes != null && !episodes.isEmpty()); + MenuItem removeAllNewFlags = menu.findItem(R.id.remove_all_new_flags_item); + if (removeAllNewFlags != null) { + removeAllNewFlags.setVisible(showOnlyNewEpisodes() && !episodes.isEmpty()); } } @@ -249,19 +232,19 @@ public class AllEpisodesFragment extends Fragment { }; markAllReadConfirmationDialog.createNewDialog().show(); return true; - case R.id.mark_all_seen_item: - ConfirmationDialog markAllSeenConfirmationDialog = new ConfirmationDialog(getActivity(), - R.string.mark_all_seen_label, - R.string.mark_all_seen_confirmation_msg) { + case R.id.remove_all_new_flags_item: + ConfirmationDialog removeAllNewFlagsConfirmationDialog = new ConfirmationDialog(getActivity(), + R.string.remove_all_new_flags_label, + R.string.remove_all_new_flags_confirmation_msg) { @Override public void onConfirmButtonPressed(DialogInterface dialog) { dialog.dismiss(); - DBWriter.markNewItemsSeen(); - Toast.makeText(getActivity(), R.string.mark_all_seen_msg, Toast.LENGTH_SHORT).show(); + DBWriter.removeAllNewFlags(); + Toast.makeText(getActivity(), R.string.removed_all_new_flags_msg, Toast.LENGTH_SHORT).show(); } }; - markAllSeenConfirmationDialog.createNewDialog().show(); + removeAllNewFlagsConfirmationDialog.createNewDialog().show(); return true; default: return false; @@ -275,111 +258,102 @@ public class AllEpisodesFragment extends Fragment { @Override public boolean onContextItemSelected(MenuItem item) { Log.d(TAG, "onContextItemSelected() called with: " + "item = [" + item + "]"); - if(!isVisible()) { + if (!getUserVisibleHint()) { return false; } - if(item.getItemId() == R.id.share_item) { + if (!isVisible()) { + return false; + } + if (item.getItemId() == R.id.share_item) { return true; // avoids that the position is reset when we need it in the submenu } - if (listAdapter == null || listAdapter.getSelectedItem() == null) { + if (listAdapter.getSelectedItem() == null) { Log.i(TAG, "Selected item or listAdapter was null, ignoring selection"); return super.onContextItemSelected(item); } FeedItem selectedItem = listAdapter.getSelectedItem(); - // Mark as seen contains UI logic specific to All/New/FavoriteSegments, + // Remove new flag contains UI logic specific to All/New/FavoriteSegments, // e.g., Undo with Snackbar, // and is handled by this class rather than the generic FeedItemMenuHandler - // Undo is useful for Mark as seen, given there is no UI to undo it otherwise, + // Undo is useful for Remove new flag, given there is no UI to undo it otherwise, // i.e., there is context menu item for Mark as new - if (R.id.mark_as_seen_item == item.getItemId()) { - markItemAsSeenWithUndo(selectedItem); + if (R.id.remove_new_flag_item == item.getItemId()) { + removeNewFlagWithUndo(selectedItem); return true; } return FeedItemMenuHandler.onMenuItemClicked(getActivity(), item.getItemId(), selectedItem); } + @NonNull @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return onCreateViewHelper(inflater, container, savedInstanceState, - R.layout.all_episodes_fragment); - } - - View onCreateViewHelper(LayoutInflater inflater, - ViewGroup container, - Bundle savedInstanceState, - int fragmentResource) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); + View root = inflater.inflate(R.layout.all_episodes_fragment, container, false); - View root = inflater.inflate(fragmentResource, container, false); - + layoutManager = new LinearLayoutManager(getActivity()); recyclerView = root.findViewById(android.R.id.list); + recyclerView.setLayoutManager(layoutManager); + recyclerView.setHasFixedSize(true); + recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build()); + recyclerView.setVisibility(View.GONE); + RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator(); if (animator instanceof SimpleItemAnimator) { ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); } - layoutManager = new LinearLayoutManager(getActivity()); - recyclerView.setLayoutManager(layoutManager); - recyclerView.setHasFixedSize(true); - recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build()); progLoading = root.findViewById(R.id.progLoading); - - if (!itemsLoaded) { - progLoading.setVisibility(View.VISIBLE); - } - - viewsCreated = true; - - if (itemsLoaded) { - onFragmentLoaded(); - } + progLoading.setVisibility(View.VISIBLE); emptyView = new EmptyViewHandler(getContext()); emptyView.attachToRecyclerView(recyclerView); + emptyView.setIcon(R.attr.feed); emptyView.setTitle(R.string.no_all_episodes_head_label); emptyView.setMessage(R.string.no_all_episodes_label); + createRecycleAdapter(recyclerView, emptyView); + emptyView.hide(); + return root; } - private void onFragmentLoaded() { - if (episodes != null && episodes.size() > 0) { - if (listAdapter == null) { - MainActivity mainActivity = (MainActivity) getActivity(); - listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, showOnlyNewEpisodes()); - listAdapter.setHasStableIds(true); - recyclerView.setAdapter(listAdapter); - emptyView.updateAdapter(listAdapter); - } - recyclerView.setVisibility(View.VISIBLE); - listAdapter.notifyDataSetChanged(); - } else { - listAdapter = null; - recyclerView.setVisibility(View.GONE); - emptyView.updateAdapter(listAdapter); + private void onFragmentLoaded(List episodes) { + this.episodes = episodes; + listAdapter.notifyDataSetChanged(); + + if (episodes.size() == 0) { + createRecycleAdapter(recyclerView, emptyView); } restoreScrollPosition(); - getActivity().supportInvalidateOptionsMenu(); - updateShowOnlyEpisodesListViewState(); + requireActivity().invalidateOptionsMenu(); + } + + /** + * Currently, we need to recreate the list adapter in order to be able to undo last item via the + * snackbar. See #3084 for details. + */ + private void createRecycleAdapter(RecyclerView recyclerView, EmptyViewHandler emptyViewHandler) { + MainActivity mainActivity = (MainActivity) getActivity(); + listAdapter = new AllEpisodesRecycleAdapter(mainActivity, itemAccess, showOnlyNewEpisodes()); + listAdapter.setHasStableIds(true); + recyclerView.setAdapter(listAdapter); + emptyViewHandler.updateAdapter(listAdapter); } private final AllEpisodesRecycleAdapter.ItemAccess itemAccess = new AllEpisodesRecycleAdapter.ItemAccess() { @Override public int getCount() { - if (episodes != null) { - return episodes.size(); - } - return 0; + return episodes.size(); } @Override public FeedItem getItem(int position) { - if (episodes != null && 0 <= position && position < episodes.size()) { + if (0 <= position && position < episodes.size()) { return episodes.get(position); } return null; @@ -387,11 +361,8 @@ public class AllEpisodesFragment extends Fragment { @Override public LongList getItemsIds() { - if(episodes == null) { - return new LongList(0); - } LongList ids = new LongList(episodes.size()); - for(FeedItem episode : episodes) { + for (FeedItem episode : episodes) { ids.add(episode.getId()); } return ids; @@ -399,12 +370,11 @@ public class AllEpisodesFragment extends Fragment { @Override public int getItemDownloadProgressPercent(FeedItem item) { - if (downloaderList != null) { - for (Downloader downloader : downloaderList) { - if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA - && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { - return downloader.getDownloadRequest().getProgressPercent(); - } + for (Downloader downloader : downloaderList) { + DownloadRequest downloadRequest = downloader.getDownloadRequest(); + if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA + && downloadRequest.getFeedfileId() == item.getMedia().getId()) { + return downloadRequest.getProgressPercent(); } } return 0; @@ -418,11 +388,8 @@ public class AllEpisodesFragment extends Fragment { @Override public LongList getQueueIds() { LongList queueIds = new LongList(); - if(episodes == null) { - return queueIds; - } - for(FeedItem item : episodes) { - if(item.isTagged(FeedItem.TAG_QUEUE)) { + for (FeedItem item : episodes) { + if (item.isTagged(FeedItem.TAG_QUEUE)) { queueIds.add(item.getId()); } } @@ -434,12 +401,6 @@ public class AllEpisodesFragment extends Fragment { @Subscribe(threadMode = ThreadMode.MAIN) public void onEventMainThread(FeedItemEvent event) { Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); - if (episodes == null) { - return; - } else if (listAdapter == null) { - loadItems(); - return; - } for (FeedItem item : event.items) { int pos = FeedItemUtil.indexOfItemWithId(episodes, item.getId()); if (pos >= 0) { @@ -464,16 +425,12 @@ public class AllEpisodesFragment extends Fragment { DownloaderUpdate update = event.update; downloaderList = update.downloaders; if (isMenuInvalidationAllowed && isUpdatingFeeds != update.feedIds.length > 0) { - getActivity().supportInvalidateOptionsMenu(); - } - if (listAdapter == null) { - loadItems(); - return; + requireActivity().invalidateOptionsMenu(); } if (update.mediaIds.length > 0) { - for(long mediaId : update.mediaIds) { + for (long mediaId : update.mediaIds) { int pos = FeedItemUtil.indexOfItemWithMediaId(episodes, mediaId); - if(pos >= 0) { + if (pos >= 0) { listAdapter.notifyItemChanged(pos); } } @@ -486,35 +443,22 @@ public class AllEpisodesFragment extends Fragment { if ((arg & EVENTS) != 0) { loadItems(); if (isUpdatingFeeds != updateRefreshMenuItemChecker.isRefreshing()) { - getActivity().supportInvalidateOptionsMenu(); + requireActivity().invalidateOptionsMenu(); } } } }; - private void updateShowOnlyEpisodesListViewState() { - } - void loadItems() { if (disposable != null) { disposable.dispose(); } - if (viewsCreated && !itemsLoaded) { - recyclerView.setVisibility(View.GONE); - emptyView.hide(); - progLoading.setVisibility(View.VISIBLE); - } disposable = Observable.fromCallable(this::loadData) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(data -> { - recyclerView.setVisibility(View.VISIBLE); progLoading.setVisibility(View.GONE); - episodes = data; - itemsLoaded = true; - if (viewsCreated) { - onFragmentLoaded(); - } + onFragmentLoaded(data); }, error -> Log.e(TAG, Log.getStackTraceString(error))); } @@ -523,12 +467,12 @@ public class AllEpisodesFragment extends Fragment { return DBReader.getRecentlyPublishedEpisodes(RECENT_EPISODES_LIMIT); } - void markItemAsSeenWithUndo(FeedItem item) { + void removeNewFlagWithUndo(FeedItem item) { if (item == null) { return; } - Log.d(TAG, "markItemAsSeenWithUndo(" + item.getId() + ")"); + Log.d(TAG, "removeNewFlagWithUndo(" + item.getId() + ")"); if (disposable != null) { disposable.dispose(); } @@ -537,14 +481,14 @@ public class AllEpisodesFragment extends Fragment { DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId()); final Handler h = new Handler(getActivity().getMainLooper()); - final Runnable r = () -> { + final Runnable r = () -> { FeedMedia media = item.getMedia(); if (media != null && media.hasAlmostEnded() && UserPreferences.isAutoDelete()) { DBWriter.deleteFeedMediaOfItem(getActivity(), media.getId()); } }; - Snackbar snackbar = Snackbar.make(getView(), getString(R.string.marked_as_seen_label), + Snackbar snackbar = Snackbar.make(getView(), getString(R.string.removed_new_flag_label), Snackbar.LENGTH_LONG); snackbar.setAction(getString(R.string.undo), v -> { DBWriter.markItemPlayed(FeedItem.NEW, item.getId()); @@ -552,7 +496,6 @@ public class AllEpisodesFragment extends Fragment { h.removeCallbacks(r); }); snackbar.show(); - h.postDelayed(r, (int)Math.ceil(snackbar.getDuration() * 1.05f)); + h.postDelayed(r, (int) Math.ceil(snackbar.getDuration() * 1.05f)); } - } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java index 0ffd1a8da..4bebfe4c9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -15,6 +15,7 @@ import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.view.EmptyViewHandler; import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -25,6 +26,7 @@ public class ChaptersFragment extends ListFragment { private ChaptersListAdapter adapter; private PlaybackController controller; private Disposable disposable; + private EmptyViewHandler emptyView; @Override @@ -36,6 +38,12 @@ public class ChaptersFragment extends ListFragment { final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); lv.setPadding(0, vertPadding, 0, vertPadding); + emptyView = new EmptyViewHandler(getContext()); + emptyView.attachToListView(lv); + emptyView.setIcon(R.attr.ic_bookmark); + emptyView.setTitle(R.string.no_chapters_head_label); + emptyView.setMessage(R.string.no_chapters_label); + adapter = new ChaptersListAdapter(getActivity(), 0, pos -> { Chapter chapter = (Chapter) getListAdapter().getItem(pos); controller.seekToChapter(chapter); @@ -118,10 +126,7 @@ public class ChaptersFragment extends ListFragment { if (adapter != null) { adapter.setMedia(media); adapter.notifyDataSetChanged(); - if (media == null || media.getChapters() == null || media.getChapters().size() == 0) { - setEmptyText(getString(R.string.no_chapters_label)); - } else { - setEmptyText(null); + if (media != null && media.getChapters() != null && media.getChapters().size() != 0) { scrollTo(getCurrentChapter(media)); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java new file mode 100644 index 000000000..1d9020f0d --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CombinedSearchFragment.java @@ -0,0 +1,173 @@ +package de.danoeh.antennapod.fragment; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.widget.SearchView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.GridView; +import android.widget.ProgressBar; +import android.widget.TextView; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.OnlineFeedViewActivity; +import de.danoeh.antennapod.adapter.itunes.ItunesAdapter; +import de.danoeh.antennapod.discovery.CombinedSearcher; +import de.danoeh.antennapod.discovery.PodcastSearchResult; +import de.danoeh.antennapod.menuhandler.MenuItemUtils; +import io.reactivex.disposables.Disposable; + +import java.util.ArrayList; +import java.util.List; + +public class CombinedSearchFragment extends Fragment { + + private static final String TAG = "CombinedSearchFragment"; + public static final String ARGUMENT_QUERY = "query"; + + /** + * Adapter responsible with the search results + */ + private ItunesAdapter adapter; + private GridView gridView; + private ProgressBar progressBar; + private TextView txtvError; + private Button butRetry; + private TextView txtvEmpty; + + /** + * List of podcasts retreived from the search + */ + private List searchResults = new ArrayList<>(); + private Disposable disposable; + + /** + * Constructor + */ + public CombinedSearchFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View root = inflater.inflate(R.layout.fragment_itunes_search, container, false); + gridView = root.findViewById(R.id.gridView); + adapter = new ItunesAdapter(getActivity(), new ArrayList<>()); + gridView.setAdapter(adapter); + + //Show information about the podcast when the list item is clicked + gridView.setOnItemClickListener((parent, view1, position, id) -> { + PodcastSearchResult podcast = searchResults.get(position); + Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); + intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl); + intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, podcast.title); + startActivity(intent); + }); + progressBar = root.findViewById(R.id.progressBar); + txtvError = root.findViewById(R.id.txtvError); + butRetry = root.findViewById(R.id.butRetry); + txtvEmpty = root.findViewById(android.R.id.empty); + + return root; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (disposable != null) { + disposable.dispose(); + } + adapter = null; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.itunes_search, menu); + MenuItem searchItem = menu.findItem(R.id.action_search); + final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); + MenuItemUtils.adjustTextColor(getActivity(), sv); + sv.setQueryHint(getString(R.string.search_label)); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + search(s); + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + getActivity().getSupportFragmentManager().popBackStack(); + return true; + } + }); + MenuItemCompat.expandActionView(searchItem); + + if (getArguments() != null && getArguments().getString(ARGUMENT_QUERY, null) != null) { + sv.setQuery(getArguments().getString(ARGUMENT_QUERY, null), true); + } + } + + private void search(String query) { + if (disposable != null) { + disposable.dispose(); + } + + showOnlyProgressBar(); + + CombinedSearcher searcher = new CombinedSearcher(getContext()); + disposable = searcher.search(query).subscribe(result -> { + searchResults = result; + progressBar.setVisibility(View.GONE); + + adapter.clear(); + adapter.addAll(searchResults); + adapter.notifyDataSetInvalidated(); + gridView.setVisibility(!searchResults.isEmpty() ? View.VISIBLE : View.GONE); + txtvEmpty.setVisibility(searchResults.isEmpty() ? View.VISIBLE : View.GONE); + + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + progressBar.setVisibility(View.GONE); + txtvError.setText(error.toString()); + txtvError.setVisibility(View.VISIBLE); + butRetry.setOnClickListener(v -> search(query)); + butRetry.setVisibility(View.VISIBLE); + }); + } + + private void showOnlyProgressBar() { + gridView.setVisibility(View.GONE); + txtvError.setVisibility(View.GONE); + butRetry.setVisibility(View.GONE); + txtvEmpty.setVisibility(View.GONE); + progressBar.setVisibility(View.VISIBLE); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index b52fd444f..705151062 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -1,7 +1,7 @@ package de.danoeh.antennapod.fragment; -import android.content.Context; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.ListFragment; import android.util.Log; import android.view.Menu; @@ -10,6 +10,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.ListView; +import java.util.ArrayList; import java.util.List; import de.danoeh.antennapod.R; @@ -27,6 +28,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_ADD_TO_QUEUE; +import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE; + /** * Displays all running downloads and provides a button to delete them */ @@ -38,24 +42,27 @@ public class CompletedDownloadsFragment extends ListFragment { EventDistributor.DOWNLOADLOG_UPDATE | EventDistributor.UNREAD_ITEMS_UPDATE; - private List items; + private List items = new ArrayList<>(); private DownloadedEpisodesListAdapter listAdapter; - - private boolean viewCreated = false; - private Disposable disposable; @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); setHasOptionsMenu(true); - loadItems(); + addVerticalPadding(); + addEmptyView(); + + listAdapter = new DownloadedEpisodesListAdapter(getActivity(), itemAccess); + setListAdapter(listAdapter); + setListShown(false); } @Override public void onStart() { super.onStart(); EventDistributor.getInstance().register(contentUpdate); + loadItems(); } @Override @@ -68,104 +75,54 @@ public class CompletedDownloadsFragment extends ListFragment { } @Override - public void onDetach() { - super.onDetach(); - if (disposable != null) { - disposable.dispose(); - } + public void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + position -= l.getHeaderViewsCount(); + long[] ids = FeedItemUtil.getIds(items); + ((MainActivity) requireActivity()).loadChildFragment(ItemFragment.newInstance(ids, position)); } @Override - public void onDestroyView() { - super.onDestroyView(); - listAdapter = null; - viewCreated = false; - if (disposable != null) { - disposable.dispose(); - } + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.downloads_completed, menu); + menu.findItem(R.id.episode_actions).setVisible(items.size() > 0); } @Override - public void onAttach(Context context) { - super.onAttach(context); - if (viewCreated && items != null) { - onFragmentLoaded(); + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.episode_actions) { + ((MainActivity) requireActivity()) + .loadChildFragment(EpisodesApplyActionFragment.newInstance(items, ACTION_DELETE | ACTION_ADD_TO_QUEUE)); + return true; } + return false; } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - // add padding - final ListView lv = getListView(); - lv.setClipToPadding(false); - final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); - lv.setPadding(0, vertPadding, 0, vertPadding); - - viewCreated = true; - if (items != null && getActivity() != null) { - onFragmentLoaded(); - } - + private void addEmptyView() { EmptyViewHandler emptyView = new EmptyViewHandler(getActivity()); + emptyView.setIcon(R.attr.av_download); emptyView.setTitle(R.string.no_comp_downloads_head_label); emptyView.setMessage(R.string.no_comp_downloads_label); emptyView.attachToListView(getListView()); } - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - super.onListItemClick(l, v, position, id); - position -= l.getHeaderViewsCount(); - long[] ids = FeedItemUtil.getIds(items); - ((MainActivity) getActivity()).loadChildFragment(ItemFragment.newInstance(ids, position)); - } - - private void onFragmentLoaded() { - if (listAdapter == null) { - listAdapter = new DownloadedEpisodesListAdapter(getActivity(), itemAccess); - setListAdapter(listAdapter); - } - setListShown(true); - listAdapter.notifyDataSetChanged(); - getActivity().supportInvalidateOptionsMenu(); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if(!isAdded()) { - return; - } - super.onCreateOptionsMenu(menu, inflater); - if(items != null) { - inflater.inflate(R.menu.downloads_completed, menu); - menu.findItem(R.id.episode_actions).setVisible(items.size() > 0); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.episode_actions: - EpisodesApplyActionFragment fragment = EpisodesApplyActionFragment - .newInstance(items, EpisodesApplyActionFragment.ACTION_DELETE | EpisodesApplyActionFragment.ACTION_ADD_TO_QUEUE); - ((MainActivity) getActivity()).loadChildFragment(fragment); - return true; - default: - return false; - } + private void addVerticalPadding() { + final ListView lv = getListView(); + lv.setClipToPadding(false); + final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); + lv.setPadding(0, vertPadding, 0, vertPadding); } private final DownloadedEpisodesListAdapter.ItemAccess itemAccess = new DownloadedEpisodesListAdapter.ItemAccess() { @Override public int getCount() { - return (items != null) ? items.size() : 0; + return items.size(); } @Override public FeedItem getItem(int position) { - if (items != null && 0 <= position && position < items.size()) { + if (0 <= position && position < items.size()) { return items.get(position); } else { return null; @@ -174,7 +131,7 @@ public class CompletedDownloadsFragment extends ListFragment { @Override public void onFeedItemSecondaryAction(FeedItem item) { - DBWriter.deleteFeedMediaOfItem(getActivity(), item.getMedia().getId()); + DBWriter.deleteFeedMediaOfItem(requireActivity(), item.getMedia().getId()); } }; @@ -191,18 +148,18 @@ public class CompletedDownloadsFragment extends ListFragment { if (disposable != null) { disposable.dispose(); } - if (items == null && viewCreated) { - setListShown(false); - } disposable = Observable.fromCallable(DBReader::getDownloadedItems) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { items = result; - if (viewCreated && getActivity() != null) { - onFragmentLoaded(); - } - }, error -> Log.e(TAG, Log.getStackTraceString(error))); + onItemsLoaded(); + }, error -> Log.e(TAG, Log.getStackTraceString(error))); } + private void onItemsLoaded() { + setListShown(true); + listAdapter.notifyDataSetChanged(); + requireActivity().invalidateOptionsMenu(); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java index 973772049..26b115b4b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -14,6 +14,7 @@ import android.view.View; import android.widget.ListView; import android.widget.TextView; +import java.util.ArrayList; import java.util.List; import de.danoeh.antennapod.R; @@ -37,18 +38,13 @@ public class DownloadLogFragment extends ListFragment { private static final String TAG = "DownloadLogFragment"; - private List downloadLog; + private List downloadLog = new ArrayList<>(); private DownloadLogAdapter adapter; - - private boolean viewsCreated = false; - private boolean itemsLoaded = false; - private Disposable disposable; @Override public void onStart() { super.onStart(); - setHasOptionsMenu(true); EventDistributor.getInstance().register(contentUpdate); loadItems(); } @@ -57,7 +53,7 @@ public class DownloadLogFragment extends ListFragment { public void onStop() { super.onStop(); EventDistributor.getInstance().unregister(contentUpdate); - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } } @@ -65,6 +61,7 @@ public class DownloadLogFragment extends ListFragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + setHasOptionsMenu(true); // add padding final ListView lv = getListView(); @@ -72,23 +69,17 @@ public class DownloadLogFragment extends ListFragment { final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); lv.setPadding(0, vertPadding, 0, vertPadding); - viewsCreated = true; - if (itemsLoaded) { - onFragmentLoaded(); - } - EmptyViewHandler emptyView = new EmptyViewHandler(getActivity()); + emptyView.setIcon(R.attr.av_download); emptyView.setTitle(R.string.no_log_downloads_head_label); emptyView.setMessage(R.string.no_log_downloads_label); emptyView.attachToListView(getListView()); + adapter = new DownloadLogAdapter(getActivity(), itemAccess); + setListAdapter(adapter); } private void onFragmentLoaded() { - if (adapter == null) { - adapter = new DownloadLogAdapter(getActivity(), itemAccess); - setListAdapter(adapter); - } setListShown(true); adapter.notifyDataSetChanged(); getActivity().supportInvalidateOptionsMenu(); @@ -129,12 +120,12 @@ public class DownloadLogFragment extends ListFragment { @Override public int getCount() { - return (downloadLog != null) ? downloadLog.size() : 0; + return downloadLog.size(); } @Override public DownloadStatus getItem(int position) { - if (downloadLog != null && 0 <= position && position < downloadLog.size()) { + if (0 <= position && position < downloadLog.size()) { return downloadLog.get(position); } else { return null; @@ -154,27 +145,23 @@ public class DownloadLogFragment extends ListFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if(!isAdded()) { + if (!isAdded()) { return; } super.onCreateOptionsMenu(menu, inflater); - if (itemsLoaded) { - MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label); - MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard}); - clearHistory.setIcon(drawables.getDrawable(0)); - drawables.recycle(); - } + MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label); + MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard}); + clearHistory.setIcon(drawables.getDrawable(0)); + drawables.recycle(); } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - if (itemsLoaded) { - MenuItem menuItem = menu.findItem(R.id.clear_history_item); - if(menuItem != null) { - menuItem.setVisible(downloadLog != null && !downloadLog.isEmpty()); - } + MenuItem menuItem = menu.findItem(R.id.clear_history_item); + if (menuItem != null) { + menuItem.setVisible(!downloadLog.isEmpty()); } } @@ -194,7 +181,7 @@ public class DownloadLogFragment extends ListFragment { } private void loadItems() { - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } disposable = Observable.fromCallable(DBReader::getDownloadLog) @@ -203,12 +190,8 @@ public class DownloadLogFragment extends ListFragment { .subscribe(result -> { if (result != null) { downloadLog = result; - itemsLoaded = true; - if (viewsCreated) { - onFragmentLoaded(); - } + onFragmentLoaded(); } }, error -> Log.e(TAG, Log.getStackTraceString(error))); } - } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java index d362d5c0b..bb029b731 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.design.widget.Snackbar; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; @@ -8,7 +9,8 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.TextView; + +import org.greenrobot.eventbus.Subscribe; import java.util.List; @@ -18,42 +20,37 @@ import de.danoeh.antennapod.core.event.FavoritesEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - /** * Like 'EpisodesFragment' except that it only shows favorite episodes and * supports swiping to remove from favorites. */ - public class FavoriteEpisodesFragment extends AllEpisodesFragment { private static final String TAG = "FavoriteEpisodesFrag"; - private static final String PREF_NAME = "PrefFavoriteEpisodesFragment"; @Override - protected boolean showOnlyNewEpisodes() { return true; } + protected boolean showOnlyNewEpisodes() { + return true; + } @Override - protected String getPrefName() { return PREF_NAME; } + protected String getPrefName() { + return PREF_NAME; + } @Subscribe public void onEvent(FavoritesEvent event) { - Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); + Log.d(TAG, String.format("onEvent() called with: event = [%s]", event)); loadItems(); } + @NonNull @Override - protected void resetViewState() { - super.resetViewState(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = super.onCreateViewHelper(inflater, container, savedInstanceState, - R.layout.all_episodes_fragment); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, container, savedInstanceState); + emptyView.setIcon(R.attr.ic_unfav); emptyView.setTitle(R.string.no_fav_episodes_head_label); emptyView.setMessage(R.string.no_fav_episodes_label); @@ -65,8 +62,8 @@ public class FavoriteEpisodesFragment extends AllEpisodesFragment { @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { - AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder)viewHolder; - Log.d(TAG, "remove(" + holder.getItemId() + ")"); + AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) viewHolder; + Log.d(TAG, String.format("remove(%s)", holder.getItemId())); if (disposable != null) { disposable.dispose(); @@ -75,8 +72,7 @@ public class FavoriteEpisodesFragment extends AllEpisodesFragment { if (item != null) { DBWriter.removeFavoriteItem(item); - Snackbar snackbar = Snackbar.make(root, getString(R.string.removed_item), - Snackbar.LENGTH_LONG); + Snackbar snackbar = Snackbar.make(root, getString(R.string.removed_item), Snackbar.LENGTH_LONG); snackbar.setAction(getString(R.string.undo), v -> DBWriter.addFavoriteItem(item)); snackbar.show(); } @@ -88,6 +84,7 @@ public class FavoriteEpisodesFragment extends AllEpisodesFragment { return root; } + @NonNull @Override protected List loadData() { return DBReader.getFavoriteItemsList(); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java index dadc596e2..9c16cfe56 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FyydSearchFragment.java @@ -16,24 +16,16 @@ import android.widget.Button; import android.widget.GridView; import android.widget.ProgressBar; import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.itunes.ItunesAdapter; -import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.discovery.FyydPodcastSearcher; +import de.danoeh.antennapod.discovery.PodcastSearchResult; import de.danoeh.antennapod.menuhandler.MenuItemUtils; -import de.mfietz.fyydlin.FyydClient; -import de.mfietz.fyydlin.FyydResponse; -import de.mfietz.fyydlin.SearchHit; -import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.Podcast; -import static java.util.Collections.emptyList; +import java.util.ArrayList; +import java.util.List; public class FyydSearchFragment extends Fragment { @@ -49,12 +41,10 @@ public class FyydSearchFragment extends Fragment { private Button butRetry; private TextView txtvEmpty; - private final FyydClient client = new FyydClient(AntennapodHttpClient.getHttpClient()); - /** * List of podcasts retreived from the search */ - private List searchResults; + private List searchResults; private Disposable disposable; /** @@ -81,7 +71,7 @@ public class FyydSearchFragment extends Fragment { //Show information about the podcast when the list item is clicked gridView.setOnItemClickListener((parent, view1, position, id) -> { - Podcast podcast = searchResults.get(position); + PodcastSearchResult podcast = searchResults.get(position); Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl); intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, podcast.title); @@ -145,20 +135,26 @@ public class FyydSearchFragment extends Fragment { disposable.dispose(); } showOnlyProgressBar(); - disposable = client.searchPodcasts(query, 10) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - progressBar.setVisibility(View.GONE); - processSearchResult(result); - }, error -> { - Log.e(TAG, Log.getStackTraceString(error)); - progressBar.setVisibility(View.GONE); - txtvError.setText(error.toString()); - txtvError.setVisibility(View.VISIBLE); - butRetry.setOnClickListener(v -> search(query)); - butRetry.setVisibility(View.VISIBLE); - }); + + FyydPodcastSearcher searcher = new FyydPodcastSearcher(); + disposable = searcher.search(query).subscribe(result -> { + searchResults = result; + progressBar.setVisibility(View.GONE); + + adapter.clear(); + adapter.addAll(searchResults); + adapter.notifyDataSetInvalidated(); + gridView.setVisibility(!searchResults.isEmpty() ? View.VISIBLE : View.GONE); + txtvEmpty.setVisibility(searchResults.isEmpty() ? View.VISIBLE : View.GONE); + + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + progressBar.setVisibility(View.GONE); + txtvError.setText(error.toString()); + txtvError.setVisibility(View.VISIBLE); + butRetry.setOnClickListener(v -> search(query)); + butRetry.setVisibility(View.VISIBLE); + }); } private void showOnlyProgressBar() { @@ -168,25 +164,4 @@ public class FyydSearchFragment extends Fragment { txtvEmpty.setVisibility(View.GONE); progressBar.setVisibility(View.VISIBLE); } - - private void processSearchResult(FyydResponse response) { - adapter.clear(); - if (!response.getData().isEmpty()) { - adapter.clear(); - searchResults = new ArrayList<>(); - for (SearchHit searchHit : response.getData()) { - Podcast podcast = Podcast.fromSearch(searchHit); - searchResults.add(podcast); - } - } else { - searchResults = emptyList(); - } - for(Podcast podcast : searchResults) { - adapter.add(podcast); - } - adapter.notifyDataSetInvalidated(); - gridView.setVisibility(!searchResults.isEmpty() ? View.VISIBLE : View.GONE); - txtvEmpty.setVisibility(searchResults.isEmpty() ? View.VISIBLE : View.GONE); - } - } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index a2472b071..432ada44e 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -36,6 +36,9 @@ import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.widget.IconButton; import org.apache.commons.lang3.ArrayUtils; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; import java.util.List; @@ -71,9 +74,6 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; /** * Displays information about a FeedItem and actions. @@ -266,7 +266,6 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - load(); } @Override @@ -274,6 +273,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { super.onStart(); EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().register(this); + load(); } @Override @@ -306,19 +306,20 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { @Override public boolean onSwipeLeftToRight() { - Log.d(TAG, "onSwipeLeftToRight()"); - feedItemPos = feedItemPos - 1; - if(feedItemPos < 0) { - feedItemPos = feedItems.length - 1; - } - load(); - return true; + return swipeFeedItem(-1); } @Override public boolean onSwipeRightToLeft() { - Log.d(TAG, "onSwipeRightToLeft()"); - feedItemPos = (feedItemPos + 1) % feedItems.length; + return swipeFeedItem(+1); + } + + private boolean swipeFeedItem(int position) { + Log.d(TAG, String.format("onSwipe() shift: %s", position)); + feedItemPos = (feedItemPos + position) % feedItems.length; + if (feedItemPos < 0) { + feedItemPos = feedItems.length - 1; + } load(); return true; } @@ -602,8 +603,9 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { @Nullable private FeedItem loadInBackground() { FeedItem feedItem = DBReader.getFeedItem(feedItems[feedItemPos]); - if (feedItem != null) { - Timeline t = new Timeline(getContext(), feedItem); + Context context = getContext(); + if (feedItem != null && context != null) { + Timeline t = new Timeline(context, feedItem); webviewData = t.processShownotes(false); } return feedItem; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java index 6a04758b9..0c75af986 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -30,6 +30,9 @@ import com.joanzapata.iconify.Iconify; import com.joanzapata.iconify.widget.IconTextView; import org.apache.commons.lang3.Validate; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; import java.util.List; @@ -71,9 +74,6 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; /** * Displays a list of FeedItems. @@ -96,8 +96,6 @@ public class ItemlistFragment extends ListFragment { private long feedID; private Feed feed; - private boolean itemsLoaded = false; - private boolean viewsCreated = false; private boolean headerCreated = false; private List downloaderList; @@ -105,7 +103,7 @@ public class ItemlistFragment extends ListFragment { private MoreContentListFooterUtil listFooter; private boolean isUpdatingFeed; - + private TextView txtvTitle; private IconTextView txtvFailure; private ImageView imgvBackground; @@ -146,9 +144,7 @@ public class ItemlistFragment extends ListFragment { super.onStart(); EventDistributor.getInstance().register(contentUpdate); EventBus.getDefault().register(this); - if (viewsCreated && itemsLoaded) { - onFragmentLoaded(); - } + loadItems(); } @Override @@ -156,7 +152,6 @@ public class ItemlistFragment extends ListFragment { super.onResume(); ((MainActivity)getActivity()).getSupportActionBar().setTitle(""); updateProgressBarVisibility(); - loadItems(); } @Override @@ -177,7 +172,6 @@ public class ItemlistFragment extends ListFragment { private void resetViewState() { adapter = null; - viewsCreated = false; listFooter = null; } @@ -190,45 +184,43 @@ public class ItemlistFragment extends ListFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if(!isAdded()) { + if (!isAdded()) { return; } super.onCreateOptionsMenu(menu, inflater); - if (itemsLoaded) { - FeedMenuHandler.onCreateOptionsMenu(inflater, menu); + FeedMenuHandler.onCreateOptionsMenu(inflater, menu); - MenuItem searchItem = menu.findItem(R.id.action_search); - final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); - MenuItemUtils.adjustTextColor(getActivity(), sv); - sv.setQueryHint(getString(R.string.search_hint)); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - sv.clearFocus(); - if (itemsLoaded) { - ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s, feed.getId())); - } - return true; + MenuItem searchItem = menu.findItem(R.id.action_search); + final SearchView sv = (SearchView) MenuItemCompat.getActionView(searchItem); + MenuItemUtils.adjustTextColor(getActivity(), sv); + sv.setQueryHint(getString(R.string.search_hint)); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + if (feed != null) { + ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(s, feed.getId())); } - - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - if(feed == null || feed.getLink() == null) { - menu.findItem(R.id.share_link_item).setVisible(false); - menu.findItem(R.id.visit_website_item).setVisible(false); + return true; } - isUpdatingFeed = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker); + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + if (feed == null || feed.getLink() == null) { + menu.findItem(R.id.share_link_item).setVisible(false); + menu.findItem(R.id.visit_website_item).setVisible(false); } + + isUpdatingFeed = MenuItemUtils.updateRefreshMenuItem(menu, R.id.refresh_item, updateRefreshMenuItemChecker); } @Override public void onPrepareOptionsMenu(Menu menu) { - if (itemsLoaded) { + if (feed != null) { FeedMenuHandler.onPrepareOptionsMenu(menu, feed); } } @@ -341,11 +333,6 @@ public class ItemlistFragment extends ListFragment { super.onViewCreated(view, savedInstanceState); registerForContextMenu(getListView()); - - viewsCreated = true; - if (itemsLoaded) { - onFragmentLoaded(); - } } @Override @@ -503,7 +490,7 @@ public class ItemlistFragment extends ListFragment { butShowInfo.setOnClickListener(v -> showFeedInfo()); imgvCover.setOnClickListener(v -> showFeedInfo()); butShowSettings.setOnClickListener(v -> { - if (viewsCreated && itemsLoaded) { + if (feed != null) { Intent startIntent = new Intent(getActivity(), FeedSettingsActivity.class); startIntent.putExtra(FeedSettingsActivity.EXTRA_FEED_ID, feed.getId()); @@ -514,7 +501,7 @@ public class ItemlistFragment extends ListFragment { } private void showFeedInfo() { - if (viewsCreated && itemsLoaded) { + if (feed != null) { Intent startIntent = new Intent(getActivity(), FeedInfoActivity.class); startIntent.putExtra(FeedInfoActivity.EXTRA_FEED_ID, feed.getId()); @@ -624,10 +611,7 @@ public class ItemlistFragment extends ListFragment { .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { feed = result.orElse(null); - itemsLoaded = true; - if (viewsCreated) { - onFragmentLoaded(); - } + onFragmentLoaded(); }, error -> Log.e(TAG, Log.getStackTraceString(error))); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java index a9f56d317..80767bef2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java @@ -20,13 +20,14 @@ import android.widget.TextView; import com.afollestad.materialdialogs.MaterialDialog; +import de.danoeh.antennapod.discovery.ItunesPodcastSearcher; +import de.danoeh.antennapod.discovery.ItunesTopListLoader; +import de.danoeh.antennapod.discovery.PodcastSearchResult; 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.util.ArrayList; import java.util.List; import java.util.Locale; @@ -46,15 +47,11 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -import static de.danoeh.antennapod.adapter.itunes.ItunesAdapter.Podcast; - //Searches iTunes store for given string and displays results in a list public class ItunesSearchFragment extends Fragment { private static final String TAG = "ItunesSearchFragment"; - private static final String API_URL = "https://itunes.apple.com/search?media=podcast&term=%s"; - /** * Adapter responsible with the search results @@ -69,21 +66,21 @@ public class ItunesSearchFragment extends Fragment { /** * List of podcasts retreived from the search */ - private List searchResults; - private List topList; + private List searchResults; + private List topList; private Disposable disposable; /** * Replace adapter data with provided search results from SearchTask. * @param result List of Podcast objects containing search results */ - private void updateData(List result) { + private void updateData(List result) { this.searchResults = result; adapter.clear(); if (result != null && result.size() > 0) { gridView.setVisibility(View.VISIBLE); txtvEmpty.setVisibility(View.GONE); - for (Podcast p : result) { + for (PodcastSearchResult p : result) { adapter.add(p); } adapter.notifyDataSetInvalidated(); @@ -117,61 +114,31 @@ public class ItunesSearchFragment extends Fragment { //Show information about the podcast when the list item is clicked gridView.setOnItemClickListener((parent, view1, position, id) -> { - Podcast podcast = searchResults.get(position); - if(podcast.feedUrl == null) { + PodcastSearchResult podcast = searchResults.get(position); + if (podcast.feedUrl == null) { return; } - if (!podcast.feedUrl.contains("itunes.apple.com")) { - Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); - intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl); - intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, "iTunes"); - startActivity(intent); - } else { - gridView.setVisibility(View.GONE); - progressBar.setVisibility(View.VISIBLE); - disposable = Single.create((SingleOnSubscribe) emitter -> { - OkHttpClient client = AntennapodHttpClient.getHttpClient(); - Request.Builder httpReq = new Request.Builder() - .url(podcast.feedUrl) - .header("User-Agent", ClientConfig.USER_AGENT); - try { - Response response = client.newCall(httpReq.build()).execute(); - if (response.isSuccessful()) { - String resultString = response.body().string(); - JSONObject result = new JSONObject(resultString); - JSONObject results = result.getJSONArray("results").getJSONObject(0); - String feedUrl = results.getString("feedUrl"); - emitter.onSuccess(feedUrl); - } else { - String prefix = getString(R.string.error_msg_prefix); - emitter.onError(new IOException(prefix + response)); - } - } catch (IOException | JSONException e) { - if (!disposable.isDisposed()) { - emitter.onError(e); - } - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(feedUrl -> { - progressBar.setVisibility(View.GONE); - gridView.setVisibility(View.VISIBLE); - Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); - intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, feedUrl); - intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, "iTunes"); - startActivity(intent); - }, error -> { - Log.e(TAG, Log.getStackTraceString(error)); - progressBar.setVisibility(View.GONE); - gridView.setVisibility(View.VISIBLE); - String prefix = getString(R.string.error_msg_prefix); - new MaterialDialog.Builder(getActivity()) - .content(prefix + " " + error.getMessage()) - .neutralText(android.R.string.ok) - .show(); - }); - } + gridView.setVisibility(View.GONE); + progressBar.setVisibility(View.VISIBLE); + ItunesTopListLoader loader = new ItunesTopListLoader(getContext()); + disposable = loader.getFeedUrl(podcast) + .subscribe(feedUrl -> { + progressBar.setVisibility(View.GONE); + gridView.setVisibility(View.VISIBLE); + Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); + intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, feedUrl); + intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, "iTunes"); + startActivity(intent); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + progressBar.setVisibility(View.GONE); + gridView.setVisibility(View.VISIBLE); + String prefix = getString(R.string.error_msg_prefix); + new MaterialDialog.Builder(getActivity()) + .content(prefix + " " + error.getMessage()) + .neutralText(android.R.string.ok) + .show(); + }); }); progressBar = root.findViewById(R.id.progressBar); txtvError = root.findViewById(R.id.txtvError); @@ -239,26 +206,9 @@ public class ItunesSearchFragment extends Fragment { butRetry.setVisibility(View.GONE); txtvEmpty.setVisibility(View.GONE); progressBar.setVisibility(View.VISIBLE); - disposable = Single.create((SingleOnSubscribe>) emitter -> { - String lang = Locale.getDefault().getLanguage(); - OkHttpClient client = AntennapodHttpClient.getHttpClient(); - String feedString; - try { - try { - feedString = getTopListFeed(client, lang); - } catch (IOException e) { - feedString = getTopListFeed(client, "us"); - } - List podcasts = parseFeed(feedString); - emitter.onSuccess(podcasts); - } catch (IOException | JSONException e) { - if (!disposable.isDisposed()) { - emitter.onError(e); - } - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) + + ItunesTopListLoader loader = new ItunesTopListLoader(getContext()); + disposable = loader.loadToplist(25) .subscribe(podcasts -> { progressBar.setVisibility(View.GONE); topList = podcasts; @@ -273,35 +223,6 @@ public class ItunesSearchFragment extends Fragment { }); } - private String getTopListFeed(OkHttpClient client, String language) throws IOException { - String url = "https://itunes.apple.com/%s/rss/toppodcasts/limit=25/explicit=true/json"; - Request.Builder httpReq = new Request.Builder() - .header("User-Agent", ClientConfig.USER_AGENT) - .url(String.format(url, language)); - - try (Response response = client.newCall(httpReq.build()).execute()) { - if (response.isSuccessful()) { - return response.body().string(); - } - String prefix = getString(R.string.error_msg_prefix); - throw new IOException(prefix + response); - } - } - - private List parseFeed(String jsonString) throws JSONException { - JSONObject result = new JSONObject(jsonString); - JSONObject feed = result.getJSONObject("feed"); - JSONArray entries = feed.getJSONArray("entry"); - - List results = new ArrayList<>(); - for (int i=0; i < entries.length(); i++) { - JSONObject json = entries.getJSONObject(i); - results.add(Podcast.fromToplist(json)); - } - - return results; - } - private void search(String query) { if (disposable != null) { disposable.dispose(); @@ -311,60 +232,19 @@ public class ItunesSearchFragment extends Fragment { butRetry.setVisibility(View.GONE); txtvEmpty.setVisibility(View.GONE); progressBar.setVisibility(View.VISIBLE); - disposable = Single.create((SingleOnSubscribe>) subscriber -> { - String encodedQuery = null; - try { - encodedQuery = URLEncoder.encode(query, "UTF-8"); - } catch (UnsupportedEncodingException e) { - // this won't ever be thrown - } - if (encodedQuery == null) { - encodedQuery = query; // failsafe - } - String formattedUrl = String.format(API_URL, encodedQuery); - - OkHttpClient client = AntennapodHttpClient.getHttpClient(); - Request.Builder httpReq = new Request.Builder() - .url(formattedUrl) - .header("User-Agent", ClientConfig.USER_AGENT); - List 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("results"); - - for (int i = 0; i < j.length(); i++) { - JSONObject podcastJson = j.getJSONObject(i); - Podcast podcast = Podcast.fromSearch(podcastJson); - podcasts.add(podcast); - } - } - else { - String prefix = getString(R.string.error_msg_prefix); - subscriber.onError(new IOException(prefix + response)); - } - } catch (IOException | JSONException e) { - subscriber.onError(e); - } - subscriber.onSuccess(podcasts); - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(podcasts -> { - progressBar.setVisibility(View.GONE); - updateData(podcasts); - }, error -> { - Log.e(TAG, Log.getStackTraceString(error)); - progressBar.setVisibility(View.GONE); - txtvError.setText(error.toString()); - txtvError.setVisibility(View.VISIBLE); - butRetry.setOnClickListener(v -> search(query)); - butRetry.setVisibility(View.VISIBLE); - }); + ItunesPodcastSearcher searcher = new ItunesPodcastSearcher(getContext()); + disposable = searcher.search(query).subscribe(podcasts -> { + progressBar.setVisibility(View.GONE); + updateData(podcasts); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + progressBar.setVisibility(View.GONE); + txtvError.setText(error.toString()); + txtvError.setVisibility(View.VISIBLE); + butRetry.setOnClickListener(v -> search(query)); + butRetry.setVisibility(View.VISIBLE); + }); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java index 5751855c7..1bf907aee 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java @@ -1,40 +1,37 @@ package de.danoeh.antennapod.fragment; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.TextView; + import java.util.List; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.AllEpisodesRecycleAdapter; -import de.danoeh.antennapod.core.event.FeedItemEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.FeedItemUtil; - /** * Like 'EpisodesFragment' except that it only shows new episodes and * supports swiping to mark as read. */ - public class NewEpisodesFragment extends AllEpisodesFragment { public static final String TAG = "NewEpisodesFragment"; private static final String PREF_NAME = "PrefNewEpisodesFragment"; - @Override - protected boolean showOnlyNewEpisodes() { return true; } @Override - protected String getPrefName() { return PREF_NAME; } + protected boolean showOnlyNewEpisodes() { + return true; + } @Override - protected void resetViewState() { - super.resetViewState(); + protected String getPrefName() { + return PREF_NAME; } @Override @@ -42,10 +39,10 @@ public class NewEpisodesFragment extends AllEpisodesFragment { return item.isNew(); } + @NonNull @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = super.onCreateViewHelper(inflater, container, savedInstanceState, - R.layout.all_episodes_fragment); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View root = super.onCreateView(inflater, container, savedInstanceState); emptyView.setTitle(R.string.no_new_episodes_head_label); emptyView.setMessage(R.string.no_new_episodes_label); @@ -57,8 +54,8 @@ public class NewEpisodesFragment extends AllEpisodesFragment { @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) { - AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder)viewHolder; - markItemAsSeenWithUndo(holder.getFeedItem()); + AllEpisodesRecycleAdapter.Holder holder = (AllEpisodesRecycleAdapter.Holder) viewHolder; + removeNewFlagWithUndo(holder.getFeedItem()); } @Override @@ -75,6 +72,7 @@ public class NewEpisodesFragment extends AllEpisodesFragment { super.onSelectedChanged(viewHolder, actionState); } + @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { @@ -94,9 +92,9 @@ public class NewEpisodesFragment extends AllEpisodesFragment { return root; } + @NonNull @Override protected List loadData() { return DBReader.getNewItemsList(); } - } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index da11383a5..e2060481f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.fragment; -import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; import android.support.annotation.NonNull; @@ -13,6 +12,10 @@ import android.view.MenuItem; import android.view.View; import android.widget.ListView; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + import java.util.List; import de.danoeh.antennapod.R; @@ -34,9 +37,6 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; public class PlaybackHistoryFragment extends ListFragment { @@ -47,22 +47,9 @@ public class PlaybackHistoryFragment extends ListFragment { private List playbackHistory; private FeedItemlistAdapter adapter; - - private boolean itemsLoaded = false; - private boolean viewsCreated = false; - private List downloaderList; - private Disposable disposable; - @Override - public void onAttach(Context context) { - super.onAttach(context); - if (viewsCreated && itemsLoaded) { - onFragmentLoaded(); - } - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -80,16 +67,17 @@ public class PlaybackHistoryFragment extends ListFragment { final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); lv.setPadding(0, vertPadding, 0, vertPadding); - viewsCreated = true; - if (itemsLoaded) { - onFragmentLoaded(); - } - EmptyViewHandler emptyView = new EmptyViewHandler(getActivity()); + emptyView.setIcon(R.attr.ic_history); emptyView.setTitle(R.string.no_history_head_label); emptyView.setMessage(R.string.no_history_label); emptyView.attachToListView(getListView()); + // played items shoudln't be transparent for this fragment since, *all* items + // in this fragment will, by definition, be played. So it serves no purpose and can make + // it harder to read. + adapter = new FeedItemlistAdapter(getActivity(), itemAccess, true, false); + setListAdapter(adapter); } @Override @@ -105,34 +93,17 @@ public class PlaybackHistoryFragment extends ListFragment { super.onStop(); EventBus.getDefault().unregister(this); EventDistributor.getInstance().unregister(contentUpdate); - if(disposable != null) { + if (disposable != null) { disposable.dispose(); } } - @Override - public void onDetach() { - super.onDetach(); - if(disposable != null) { - disposable.dispose(); - } - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - adapter = null; - viewsCreated = false; - } - @Subscribe(sticky = true) public void onEvent(DownloadEvent event) { Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; downloaderList = update.downloaders; - if (adapter != null) { - adapter.notifyDataSetChanged(); - } + adapter.notifyDataSetChanged(); } @Override @@ -145,27 +116,23 @@ public class PlaybackHistoryFragment extends ListFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if(!isAdded()) { + if (!isAdded()) { return; } super.onCreateOptionsMenu(menu, inflater); - if (itemsLoaded) { - MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label); - MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard}); - clearHistory.setIcon(drawables.getDrawable(0)); - drawables.recycle(); - } + MenuItem clearHistory = menu.add(Menu.NONE, R.id.clear_history_item, Menu.CATEGORY_CONTAINER, R.string.clear_history_label); + MenuItemCompat.setShowAsAction(clearHistory, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + TypedArray drawables = getActivity().obtainStyledAttributes(new int[]{R.attr.content_discard}); + clearHistory.setIcon(drawables.getDrawable(0)); + drawables.recycle(); } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); - if (itemsLoaded) { - MenuItem menuItem = menu.findItem(R.id.clear_history_item); - if (menuItem != null) { - menuItem.setVisible(playbackHistory != null && !playbackHistory.isEmpty()); - } + MenuItem menuItem = menu.findItem(R.id.clear_history_item); + if (menuItem != null) { + menuItem.setVisible(playbackHistory != null && !playbackHistory.isEmpty()); } } @@ -211,14 +178,6 @@ public class PlaybackHistoryFragment extends ListFragment { }; private void onFragmentLoaded() { - if (adapter == null) { - // played items shoudln't be transparent for this fragment since, *all* items - // in this fragment will, by definition, be played. So it serves no purpose and can make - // it harder to read. - adapter = new FeedItemlistAdapter(getActivity(), itemAccess, true, false); - setListAdapter(adapter); - } - setListShown(true); adapter.notifyDataSetChanged(); getActivity().supportInvalidateOptionsMenu(); } @@ -277,10 +236,7 @@ public class PlaybackHistoryFragment extends ListFragment { .subscribe(result -> { if (result != null) { playbackHistory = result; - itemsLoaded = true; - if (viewsCreated) { - onFragmentLoaded(); - } + onFragmentLoaded(); } }, error -> Log.e(TAG, Log.getStackTraceString(error))); } @@ -291,5 +247,4 @@ public class PlaybackHistoryFragment extends ListFragment { DBReader.loadAdditionalFeedItemListData(history); return history; } - } 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 f3421c8fd..4947d5a87 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -49,6 +49,7 @@ import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.QueueSorter; +import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.MenuItemUtils; @@ -61,6 +62,9 @@ import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; +import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_DELETE; +import static de.danoeh.antennapod.dialog.EpisodesApplyActionFragment.ACTION_REMOVE_FROM_QUEUE; + /** * Shows all items in the queue */ @@ -323,6 +327,10 @@ public class QueueFragment extends Fragment { }; conDialog.createNewDialog().show(); return true; + case R.id.episode_actions: + ((MainActivity) requireActivity()) .loadChildFragment( + EpisodesApplyActionFragment.newInstance(queue, ACTION_DELETE | ACTION_REMOVE_FROM_QUEUE)); + return true; case R.id.queue_sort_episode_title_asc: QueueSorter.sort(QueueSorter.Rule.EPISODE_TITLE_ASC, true); return true; @@ -518,6 +526,7 @@ public class QueueFragment extends Fragment { emptyView = new EmptyViewHandler(getContext()); emptyView.attachToRecyclerView(recyclerView); + emptyView.setIcon(R.attr.stat_playlist); emptyView.setTitle(R.string.no_items_header_label); emptyView.setMessage(R.string.no_items_label); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java new file mode 100644 index 000000000..e4213cc6b --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java @@ -0,0 +1,119 @@ +package de.danoeh.antennapod.fragment; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.GridView; +import android.widget.ProgressBar; +import android.widget.TextView; +import com.afollestad.materialdialogs.MaterialDialog; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.activity.OnlineFeedViewActivity; +import de.danoeh.antennapod.adapter.FeedDiscoverAdapter; +import de.danoeh.antennapod.discovery.ItunesTopListLoader; +import de.danoeh.antennapod.discovery.PodcastSearchResult; +import io.reactivex.disposables.Disposable; + +import java.util.ArrayList; +import java.util.List; + + +public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.OnItemClickListener { + private static final String TAG = "FeedDiscoveryFragment"; + + private ProgressBar progressBar; + private Disposable disposable; + private FeedDiscoverAdapter adapter; + private GridView subscriptionGridLayout; + private TextView errorTextView; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + View root = inflater.inflate(R.layout.quick_feed_discovery, container, false); + View discoverMore = root.findViewById(R.id.discover_more); + discoverMore.setOnClickListener(v -> + ((MainActivity) getActivity()).loadChildFragment(new ItunesSearchFragment())); + + subscriptionGridLayout = root.findViewById(R.id.discover_grid); + progressBar = root.findViewById(R.id.discover_progress_bar); + errorTextView = root.findViewById(R.id.discover_error); + + adapter = new FeedDiscoverAdapter((MainActivity) getActivity()); + subscriptionGridLayout.setAdapter(adapter); + subscriptionGridLayout.setOnItemClickListener(this); + + // Fill with dummy elements to have a fixed height and + // prevent the UI elements below from jumping on slow connections + List dummies = new ArrayList<>(); + for (int i = 0; i < 8; i++) { + dummies.add(PodcastSearchResult.dummy()); + } + adapter.updateData(dummies); + + loadToplist(); + + return root; + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (disposable != null) { + disposable.dispose(); + } + } + + private void loadToplist() { + progressBar.setVisibility(View.VISIBLE); + subscriptionGridLayout.setVisibility(View.INVISIBLE); + errorTextView.setVisibility(View.GONE); + + ItunesTopListLoader loader = new ItunesTopListLoader(getContext()); + disposable = loader.loadToplist(8) + .subscribe(podcasts -> { + errorTextView.setVisibility(View.GONE); + progressBar.setVisibility(View.GONE); + subscriptionGridLayout.setVisibility(View.VISIBLE); + adapter.updateData(podcasts); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + errorTextView.setText(error.getLocalizedMessage()); + errorTextView.setVisibility(View.VISIBLE); + progressBar.setVisibility(View.GONE); + subscriptionGridLayout.setVisibility(View.INVISIBLE); + }); + } + + @Override + public void onItemClick(AdapterView parent, final View view, int position, long id) { + PodcastSearchResult podcast = adapter.getItem(position); + if (podcast.feedUrl == null) { + return; + } + view.setAlpha(0.5f); + ItunesTopListLoader loader = new ItunesTopListLoader(getContext()); + disposable = loader.getFeedUrl(podcast) + .subscribe(feedUrl -> { + view.setAlpha(1f); + Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); + intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, feedUrl); + intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label)); + startActivity(intent); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + view.setAlpha(1f); + String prefix = getString(R.string.error_msg_prefix); + new MaterialDialog.Builder(getActivity()) + .content(prefix + " " + error.getMessage()) + .neutralText(android.R.string.ok) + .show(); + }); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java index 718502ea2..2a7f7d12b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java @@ -7,6 +7,10 @@ import android.view.View; import android.widget.ListView; import android.widget.Toast; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; + +import java.util.ArrayList; import java.util.List; import de.danoeh.antennapod.R; @@ -21,8 +25,6 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.view.EmptyViewHandler; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; /** * Displays all running downloads and provides actions to cancel them @@ -32,7 +34,7 @@ public class RunningDownloadsFragment extends ListFragment { private static final String TAG = "RunningDownloadsFrag"; private DownloadlistAdapter adapter; - private List downloaderList; + private List downloaderList = new ArrayList<>(); @Override public void onViewCreated(View view, Bundle savedInstanceState) { @@ -48,6 +50,7 @@ public class RunningDownloadsFragment extends ListFragment { setListAdapter(adapter); EmptyViewHandler emptyView = new EmptyViewHandler(getActivity()); + emptyView.setIcon(R.attr.av_download); emptyView.setTitle(R.string.no_run_downloads_head_label); emptyView.setMessage(R.string.no_run_downloads_label); emptyView.attachToListView(getListView()); @@ -70,7 +73,6 @@ public class RunningDownloadsFragment extends ListFragment { public void onDestroy() { super.onDestroy(); setListAdapter(null); - adapter = null; } @Subscribe(sticky = true) @@ -78,21 +80,18 @@ public class RunningDownloadsFragment extends ListFragment { Log.d(TAG, "onEvent() called with: " + "event = [" + event + "]"); DownloaderUpdate update = event.update; downloaderList = update.downloaders; - if (adapter != null) { - adapter.notifyDataSetChanged(); - } + adapter.notifyDataSetChanged(); } - private final DownloadlistAdapter.ItemAccess itemAccess = new DownloadlistAdapter.ItemAccess() { @Override public int getCount() { - return (downloaderList != null) ? downloaderList.size() : 0; + return downloaderList.size(); } @Override public Downloader getItem(int position) { - if (downloaderList != null && 0 <= position && position < downloaderList.size()) { + if (0 <= position && position < downloaderList.size()) { return downloaderList.get(position); } else { return null; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index 1d7ac8824..0892bce0a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -14,6 +14,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.ListView; +import java.util.ArrayList; import java.util.List; import de.danoeh.antennapod.R; @@ -40,11 +41,7 @@ public class SearchFragment extends ListFragment { private static final String ARG_FEED = "feed"; private SearchlistAdapter searchAdapter; - private List searchResults; - - private boolean viewCreated = false; - private boolean itemsLoaded = false; - + private List searchResults = new ArrayList<>(); private Disposable disposable; /** @@ -74,13 +71,13 @@ public class SearchFragment extends ListFragment { super.onCreate(savedInstanceState); setRetainInstance(true); setHasOptionsMenu(true); - search(); } @Override public void onStart() { super.onStart(); EventDistributor.getInstance().register(contentUpdate); + search(); } @Override @@ -92,21 +89,6 @@ public class SearchFragment extends ListFragment { EventDistributor.getInstance().unregister(contentUpdate); } - @Override - public void onDetach() { - super.onDetach(); - if(disposable != null) { - disposable.dispose(); - } - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - searchAdapter = null; - viewCreated = false; - } - @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); @@ -118,10 +100,9 @@ public class SearchFragment extends ListFragment { lv.setPadding(0, vertPadding, 0, vertPadding); ((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(R.string.search_label); - viewCreated = true; - if (itemsLoaded) { - onFragmentLoaded(); - } + + searchAdapter = new SearchlistAdapter(getActivity(), itemAccess); + setListAdapter(searchAdapter); } @Override @@ -142,28 +123,26 @@ public class SearchFragment extends ListFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); - if (itemsLoaded) { - MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label); - MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - final SearchView sv = new SearchView(getActivity()); - sv.setQueryHint(getString(R.string.search_hint)); - sv.setQuery(getArguments().getString(ARG_QUERY), false); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - getArguments().putString(ARG_QUERY, s); - itemsLoaded = false; - search(); - return true; - } + MenuItem item = menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label); + MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); + final SearchView sv = new SearchView(getActivity()); + sv.setQueryHint(getString(R.string.search_hint)); + sv.setQuery(getArguments().getString(ARG_QUERY), false); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String s) { + sv.clearFocus(); + getArguments().putString(ARG_QUERY, s); + search(); + return true; + } - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - MenuItemCompat.setActionView(item, sv); - } + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + MenuItemCompat.setActionView(item, sv); } private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @@ -176,14 +155,9 @@ public class SearchFragment extends ListFragment { } }; - private void onFragmentLoaded() { - if (searchAdapter == null) { - searchAdapter = new SearchlistAdapter(getActivity(), itemAccess); - setListAdapter(searchAdapter); - } + private void onSearchResults(List results) { + searchResults = results; searchAdapter.notifyDataSetChanged(); - setListShown(true); - String query = getArguments().getString(ARG_QUERY); setEmptyText(getString(R.string.no_results_for_query, query)); } @@ -191,12 +165,12 @@ public class SearchFragment extends ListFragment { private final SearchlistAdapter.ItemAccess itemAccess = new SearchlistAdapter.ItemAccess() { @Override public int getCount() { - return (searchResults != null) ? searchResults.size() : 0; + return searchResults.size(); } @Override public SearchResult getItem(int position) { - if (searchResults != null && 0 <= position && position < searchResults.size()) { + if (0 <= position && position < searchResults.size()) { return searchResults.get(position); } else { return null; @@ -204,24 +178,14 @@ public class SearchFragment extends ListFragment { } }; - private void search() { if(disposable != null) { disposable.dispose(); } - if (viewCreated && !itemsLoaded) { - setListShown(false); - } disposable = Observable.fromCallable(this::performSearch) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - itemsLoaded = true; - searchResults = result; - if (viewCreated) { - onFragmentLoaded(); - } - }, error -> Log.e(TAG, Log.getStackTraceString(error))); + .subscribe(this::onSearchResults, error -> Log.e(TAG, Log.getStackTraceString(error))); } @NonNull @@ -232,5 +196,4 @@ public class SearchFragment extends ListFragment { Context context = getActivity(); return FeedSearcher.performSearch(context, query, feed); } - } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java index 75da522d1..15c6052a9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -1,9 +1,11 @@ package de.danoeh.antennapod.fragment; +import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; +import android.support.annotation.StringRes; import android.support.v4.app.Fragment; import android.util.Log; import android.view.ContextMenu; @@ -16,6 +18,8 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.GridView; +import java.util.concurrent.Callable; + import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.SubscriptionsAdapter; @@ -56,16 +60,13 @@ public class SubscriptionFragment extends Fragment { private Disposable disposable; private SharedPreferences prefs; - public SubscriptionFragment() { - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); setHasOptionsMenu(true); - prefs = getActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE); + prefs = requireActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE); } @Override @@ -123,23 +124,25 @@ public class SubscriptionFragment extends Fragment { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); subscriptionAdapter = new SubscriptionsAdapter((MainActivity)getActivity(), itemAccess); - subscriptionGridLayout.setAdapter(subscriptionAdapter); - - loadSubscriptions(); - subscriptionGridLayout.setOnItemClickListener(subscriptionAdapter); if (getActivity() instanceof MainActivity) { ((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.subscriptions_label); } - - EventDistributor.getInstance().register(contentUpdate); } @Override - public void onDestroy() { - super.onDestroy(); + public void onStart() { + super.onStart(); + EventDistributor.getInstance().register(contentUpdate); + loadSubscriptions(); + } + + @Override + public void onStop() { + super.onStop(); + EventDistributor.getInstance().unregister(contentUpdate); if(disposable != null) { disposable.dispose(); } @@ -172,7 +175,7 @@ public class SubscriptionFragment extends Fragment { Feed feed = (Feed)selectedObject; - MenuInflater inflater = getActivity().getMenuInflater(); + MenuInflater inflater = requireActivity().getMenuInflater(); inflater.inflate(R.menu.nav_feed_context, menu); menu.setHeaderTitle(feed.getTitle()); @@ -182,7 +185,6 @@ public class SubscriptionFragment extends Fragment { @Override public boolean onContextItemSelected(MenuItem item) { - final int position = mPosition; mPosition = -1; // reset if(position < 0) { @@ -197,84 +199,73 @@ public class SubscriptionFragment extends Fragment { Feed feed = (Feed)selectedObject; switch(item.getItemId()) { - case R.id.mark_all_seen_item: - ConfirmationDialog markAllSeenConfirmationDialog = new ConfirmationDialog(getActivity(), - R.string.mark_all_seen_label, - R.string.mark_all_seen_confirmation_msg) { - - @Override - public void onConfirmButtonPressed(DialogInterface dialog) { - dialog.dismiss(); - - Observable.fromCallable(() -> DBWriter.markFeedSeen(feed.getId())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> loadSubscriptions(), - error -> Log.e(TAG, Log.getStackTraceString(error))); - } - }; - markAllSeenConfirmationDialog.createNewDialog().show(); + case R.id.remove_all_new_flags_item: + displayConfirmationDialog( + R.string.remove_all_new_flags_label, + R.string.remove_all_new_flags_confirmation_msg, + () -> DBWriter.removeFeedNewFlag(feed.getId())); return true; case R.id.mark_all_read_item: - ConfirmationDialog markAllReadConfirmationDialog = new ConfirmationDialog(getActivity(), + displayConfirmationDialog( R.string.mark_all_read_label, - R.string.mark_all_read_confirmation_msg) { - - @Override - public void onConfirmButtonPressed(DialogInterface dialog) { - dialog.dismiss(); - Observable.fromCallable(() -> DBWriter.markFeedRead(feed.getId())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> loadSubscriptions(), - error -> Log.e(TAG, Log.getStackTraceString(error))); - } - }; - markAllReadConfirmationDialog.createNewDialog().show(); + R.string.mark_all_read_confirmation_msg, + () -> DBWriter.markFeedRead(feed.getId())); return true; case R.id.rename_item: new RenameFeedDialog(getActivity(), feed).show(); return true; case R.id.remove_item: - final FeedRemover remover = new FeedRemover(getContext(), feed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - loadSubscriptions(); - } - }; - ConfirmationDialog conDialog = new ConfirmationDialog(getContext(), - R.string.remove_feed_label, - getString(R.string.feed_delete_confirmation_msg, feed.getTitle())) { - @Override - public void onConfirmButtonPressed( - DialogInterface dialog) { - dialog.dismiss(); - long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId(); - if (mediaId > 0 && - FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) { - Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); - remover.skipOnCompletion = true; - int playerStatus = PlaybackPreferences.getCurrentPlayerStatus(); - if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) { - IntentUtils.sendLocalBroadcast(getContext(), PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE); - - } - } - remover.executeAsync(); - } - }; - conDialog.createNewDialog().show(); + displayRemoveFeedDialog(feed); return true; default: return super.onContextItemSelected(item); } } - @Override - public void onResume() { - super.onResume(); - loadSubscriptions(); + private void displayRemoveFeedDialog(Feed feed) { + final FeedRemover remover = new FeedRemover(getContext(), feed) { + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + loadSubscriptions(); + } + }; + + String message = getString(R.string.feed_delete_confirmation_msg, feed.getTitle()); + ConfirmationDialog dialog = new ConfirmationDialog(getContext(), R.string.remove_feed_label, message) { + @Override + public void onConfirmButtonPressed(DialogInterface clickedDialog) { + clickedDialog.dismiss(); + long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId(); + if (mediaId > 0 && FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) { + Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); + remover.skipOnCompletion = true; + int playerStatus = PlaybackPreferences.getCurrentPlayerStatus(); + if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) { + IntentUtils.sendLocalBroadcast(getContext(), PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE); + + } + } + remover.executeAsync(); + } + }; + dialog.createNewDialog().show(); + } + + private void displayConfirmationDialog(@StringRes int title, @StringRes int message, Callable task) { + ConfirmationDialog dialog = new ConfirmationDialog(getActivity(), title, message) { + @Override + @SuppressLint("CheckResult") + public void onConfirmButtonPressed(DialogInterface clickedDialog) { + clickedDialog.dismiss(); + Observable.fromCallable(task) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> loadSubscriptions(), + error -> Log.e(TAG, Log.getStackTraceString(error))); + } + }; + dialog.createNewDialog().show(); } private final EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/FlattrPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/FlattrPreferencesFragment.java deleted file mode 100644 index 152c3da87..000000000 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/FlattrPreferencesFragment.java +++ /dev/null @@ -1,61 +0,0 @@ -package de.danoeh.antennapod.fragment.preferences; - -import android.os.Bundle; -import android.support.v7.preference.PreferenceFragmentCompat; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; -import de.danoeh.antennapod.dialog.AutoFlattrPreferenceDialog; - -public class FlattrPreferencesFragment extends PreferenceFragmentCompat { - private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate"; - private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess"; - private static final String PREF_AUTO_FLATTR_PREFS = "prefAutoFlattrPrefs"; - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.preferences_flattr); - setupFlattrScreen(); - } - - @Override - public void onResume() { - super.onResume(); - checkFlattrItemVisibility(); - } - - private void setupFlattrScreen() { - findPreference(PREF_FLATTR_REVOKE).setOnPreferenceClickListener( - preference -> { - FlattrUtils.revokeAccessToken(getActivity()); - checkFlattrItemVisibility(); - return true; - } - ); - - findPreference(PREF_AUTO_FLATTR_PREFS) - .setOnPreferenceClickListener(preference -> { - AutoFlattrPreferenceDialog.newAutoFlattrPreferenceDialog(getActivity(), - new AutoFlattrPreferenceDialog.AutoFlattrPreferenceDialogInterface() { - @Override - public void onCancelled() { - - } - - @Override - public void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue) { - UserPreferences.setAutoFlattrSettings(autoFlattrEnabled, autoFlattrValue); - checkFlattrItemVisibility(); - } - }); - return true; - }); - } - - private void checkFlattrItemVisibility() { - boolean hasFlattrToken = FlattrUtils.hasToken(); - findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken); - findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken); - findPreference(PREF_AUTO_FLATTR_PREFS).setEnabled(hasFlattrToken); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/IntegrationsPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/IntegrationsPreferencesFragment.java index 805d84215..229274b76 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/IntegrationsPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/IntegrationsPreferencesFragment.java @@ -4,10 +4,8 @@ import android.os.Bundle; import android.support.v7.preference.PreferenceFragmentCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; public class IntegrationsPreferencesFragment extends PreferenceFragmentCompat { - private static final String PREF_SCREEN_FLATTR = "prefFlattrSettings"; private static final String PREF_SCREEN_GPODDER = "prefGpodderSettings"; @Override @@ -17,19 +15,9 @@ public class IntegrationsPreferencesFragment extends PreferenceFragmentCompat { } private void setupIntegrationsScreen() { - findPreference(PREF_SCREEN_FLATTR).setOnPreferenceClickListener(preference -> { - ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_flattr); - return true; - }); findPreference(PREF_SCREEN_GPODDER).setOnPreferenceClickListener(preference -> { ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_gpodder); return true; }); } - - @Override - public void onResume() { - super.onResume(); - findPreference(PREF_SCREEN_FLATTR).setEnabled(FlattrUtils.hasAPICredentials()); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java index 5a4866206..701d21ce0 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java @@ -143,8 +143,5 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { config.index(R.xml.preferences_gpodder) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_integrations)) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_gpodder)); - config.index(R.xml.preferences_flattr) - .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_integrations)) - .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_flattr)); } } diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java index 2886a7e33..0c7622a47 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -124,10 +124,6 @@ public class FeedItemMenuHandler { mi.setItemVisibility(R.id.deactivate_auto_download, false); } - if (selectedItem.getPaymentLink() == null || !selectedItem.getFlattrStatus().flattrable()) { - mi.setItemVisibility(R.id.support_item, false); - } - boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE); mi.setItemVisibility(R.id.add_to_favorites_item, !isFavorite); mi.setItemVisibility(R.id.remove_from_favorites_item, isFavorite); @@ -230,9 +226,6 @@ public class FeedItemMenuHandler { Toast.LENGTH_SHORT).show(); } break; - case R.id.support_item: - DBTasks.flattrItemIfLoggedIn(context, selectedItem); - break; case R.id.share_link_item: ShareUtils.shareFeedItemLink(context, selectedItem); break; diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java index bd4fe9bcf..0928cfd62 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -46,11 +46,6 @@ public class FeedMenuHandler { } Log.d(TAG, "Preparing options menu"); - if (selectedFeed.getPaymentLink() != null && selectedFeed.getFlattrStatus().flattrable()) { - menu.findItem(R.id.support_item).setVisible(true); - } else { - menu.findItem(R.id.support_item).setVisible(false); - } menu.findItem(R.id.refresh_complete_item).setVisible(selectedFeed.isPaged()); @@ -98,9 +93,6 @@ public class FeedMenuHandler { Toast.LENGTH_SHORT).show(); } break; - case R.id.support_item: - DBTasks.flattrFeedIfLoggedIn(context, selectedFeed); - break; case R.id.share_link_item: ShareUtils.shareFeedlink(context, selectedFeed); break; diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java index 93b326698..5866f8715 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -2,28 +2,51 @@ package de.danoeh.antennapod.preferences; import android.content.Context; import android.content.SharedPreferences; +import android.preference.PreferenceManager; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.gui.NotificationUtils; public class PreferenceUpgrader { private static final String PREF_CONFIGURED_VERSION = "configuredVersion"; private static final String PREF_NAME = "PreferenceUpgrader"; + private static SharedPreferences prefs; public static void checkUpgrades(Context context) { - SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); - int oldVersion = prefs.getInt(PREF_CONFIGURED_VERSION, 1070200); + prefs = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences upgraderPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + int oldVersion = upgraderPrefs.getInt(PREF_CONFIGURED_VERSION, 1070200); int newVersion = BuildConfig.VERSION_CODE; if (oldVersion != newVersion) { - prefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply(); + NotificationUtils.createChannels(context); + + upgraderPrefs.edit().putInt(PREF_CONFIGURED_VERSION, newVersion).apply(); upgrade(oldVersion); } } private static void upgrade(int oldVersion) { + if (oldVersion < 1070196) { + // migrate episode cleanup value (unit changed from days to hours) + int oldValueInDays = UserPreferences.getEpisodeCleanupValue(); + if (oldValueInDays > 0) { + UserPreferences.setEpisodeCleanupValue(oldValueInDays * 24); + } // else 0 or special negative values, no change needed + } + if (oldVersion < 1070197) { + if (prefs.getBoolean("prefMobileUpdate", false)) { + prefs.edit().putString(UserPreferences.PREF_MOBILE_UPDATE, "everything").apply(); + } + } if (oldVersion < 1070300) { UserPreferences.restartUpdateAlarm(); + + if (UserPreferences.getMediaPlayer().equals("builtin")) { + prefs.edit().putString(UserPreferences.PREF_MEDIA_PLAYER, + UserPreferences.PREF_MEDIA_PLAYER_EXOPLAYER).apply(); + } } } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java b/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java index 42c11bc8e..8b886e699 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/view/EmptyViewHandler.java @@ -1,9 +1,14 @@ package de.danoeh.antennapod.view; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.AttrRes; +import android.support.v4.content.ContextCompat; import android.support.v7.widget.RecyclerView; +import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; @@ -15,14 +20,18 @@ public class EmptyViewHandler { private RecyclerView recyclerView; private RecyclerView.Adapter adapter; + private final Context context; private final View emptyView; private final TextView tvTitle; private final TextView tvMessage; + private final ImageView ivIcon; public EmptyViewHandler(Context context) { emptyView = View.inflate(context, R.layout.empty_view_layout, null); + this.context = context; tvTitle = emptyView.findViewById(R.id.emptyViewTitle); tvMessage = emptyView.findViewById(R.id.emptyViewMessage); + ivIcon = emptyView.findViewById(R.id.emptyViewIcon); } public void setTitle(int title) { @@ -33,6 +42,14 @@ public class EmptyViewHandler { tvMessage.setText(message); } + public void setIcon(@AttrRes int iconAttr) { + TypedValue typedValue = new TypedValue(); + context.getTheme().resolveAttribute(iconAttr, typedValue, true); + Drawable d = ContextCompat.getDrawable(context, typedValue.resourceId); + ivIcon.setImageDrawable(d); + ivIcon.setVisibility(View.VISIBLE); + } + public void hide() { emptyView.setVisibility(View.GONE); } diff --git a/app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java b/app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java new file mode 100644 index 000000000..37792b4d1 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java @@ -0,0 +1,35 @@ +package de.danoeh.antennapod.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.GridView; + +/** + * Source: https://stackoverflow.com/a/46350213/ + */ +public class WrappingGridView extends GridView { + + public WrappingGridView(Context context) { + super(context); + } + + public WrappingGridView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public WrappingGridView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int heightSpec = heightMeasureSpec; + if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) { + // The great Android "hackatlon", the love, the magic. + // The two leftmost bits in the height measure spec have + // a special meaning, hence we can't use them to describe height. + heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + } + super.onMeasure(widthMeasureSpec, heightSpec); + } +} diff --git a/app/src/main/play/listings/en-US/full-description.txt b/app/src/main/play/listings/en-US/full-description.txt index 2f5b4b2ff..c562387af 100644 --- a/app/src/main/play/listings/en-US/full-description.txt +++ b/app/src/main/play/listings/en-US/full-description.txt @@ -1,5 +1,5 @@ AntennaPod is a podcast manager and player that gives you instant access to millions of free and paid podcasts, from independent podcasters to large publishing houses such as the BBC, NPR and CNN. Add, import and export their feeds hassle-free using the iTunes podcast database, OPML files or simple RSS URLs. Save effort, battery power and mobile data usage with powerful automation controls for downloading episodes (specify times, intervals and WiFi networks) and deleting episodes (based on your favourites and delay settings).
-But most importantly: Download, stream or queue episodes and enjoy them the way you like with adjustable playback speeds, chapter support and a sleep timer. You can even show your love to the content creators with our Flattr integration. +But most importantly: Download, stream or queue episodes and enjoy them the way you like with adjustable playback speeds, chapter support and a sleep timer. Made by podcast-enthousiast, AntennaPod is free in all senses of the word: open source, no costs, no ads. @@ -15,7 +15,6 @@ KEEP TRACK, SHARE & APPRECIATE
• Keep track of the best of the best by marking episodes as favourites
• Find that one episode through the playback history or by searching (titles and shownotes)
• Share episodes and feeds through advanced social media and email options, the gPodder.net services and via OPML export
-• Support content creators with Flattr integration including automatic flattring CONTROL THE SYSTEM
• Take control over automated downloading: choose feeds, exclude mobile networks, select specific WiFi networks, require the phone to be charging and set times or intervals
diff --git a/app/src/main/res/layout/addfeed.xml b/app/src/main/res/layout/addfeed.xml index 33951e060..011aa4c8b 100644 --- a/app/src/main/res/layout/addfeed.xml +++ b/app/src/main/res/layout/addfeed.xml @@ -1,100 +1,173 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="vertical"> - - + android:orientation="vertical" + android:padding="8dp"> - + -