diff options
Diffstat (limited to 'app/src/main/java')
48 files changed, 1476 insertions, 1340 deletions
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<AllEpisodesR }; FeedItemMenuHandler.onPrepareMenu(contextMenuInterface, item, true, null); - contextMenuInterface.setItemVisibility(R.id.mark_as_seen_item, item.isNew()); + contextMenuInterface.setItemVisibility(R.id.remove_new_flag_item, item.isNew()); } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DataFolderAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DataFolderAdapter.java index 636a23088..909fd6459 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DataFolderAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DataFolderAdapter.java @@ -49,9 +49,10 @@ public class DataFolderAdapter extends RecyclerView.Adapter<DataFolderAdapter.Vi public void onBindViewHolder(@NonNull ViewHolder holder, int position) { StoragePath storagePath = entries.get(position); String freeSpace = Converter.byteToString(storagePath.getAvailableSpace()); + String totalSpace = Converter.byteToString(storagePath.getTotalSpace()); holder.path.setText(storagePath.getShortPath()); - holder.size.setText(String.format(freeSpaceString, freeSpace)); + holder.size.setText(String.format(freeSpaceString, freeSpace, totalSpace)); holder.progressBar.setProgress(storagePath.getUsagePercentage()); holder.root.setOnClickListener((View v) -> 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<MainActivity> mainActivityRef; + private final List<PodcastSearchResult> data = new ArrayList<>(); + + public FeedDiscoverAdapter(MainActivity mainActivity) { + this.mainActivityRef = new WeakReference<>(mainActivity); + } + + public void updateData(List<PodcastSearchResult> 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<ItunesAdapter.Podcast> { +public class ItunesAdapter extends ArrayAdapter<PodcastSearchResult> { /** * Related Context */ @@ -33,7 +28,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { /** * List holding the podcasts found in the search */ - private final List<Podcast> data; + private final List<PodcastSearchResult> data; /** * Constructor. @@ -41,7 +36,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { * @param context Related context * @param objects Search result */ - public ItunesAdapter(Context context, List<Podcast> objects) { + public ItunesAdapter(Context context, List<PodcastSearchResult> objects) { super(context, 0, objects); this.data = objects; this.context = context; @@ -51,7 +46,7 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { @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; @@ -94,75 +89,6 @@ public class ItunesAdapter extends ArrayAdapter<ItunesAdapter.Podcast> { } /** - * 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 */ static class PodcastViewHolder { 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<Pair<PodcastSearcher, Float>> 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<List<PodcastSearchResult>> search(String query) { + ArrayList<Disposable> disposables = new ArrayList<>(); + List<List<PodcastSearchResult>> singleResults = new ArrayList<>(Collections.nCopies(searchProviders.size(), null)); + CountDownLatch latch = new CountDownLatch(searchProviders.size()); + for (int i = 0; i < searchProviders.size(); i++) { + Pair<PodcastSearcher, Float> 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<List<PodcastSearchResult>>) subscriber -> { + latch.await(); + List<PodcastSearchResult> results = weightSearchResults(singleResults); + subscriber.onSuccess(results); + }) + .doOnDispose(() -> { + for (Disposable disposable : disposables) { + disposable.dispose(); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + private List<PodcastSearchResult> weightSearchResults(List<List<PodcastSearchResult>> singleResults) { + HashMap<String, Float> resultRanking = new HashMap<>(); + HashMap<String, PodcastSearchResult> urlToResult = new HashMap<>(); + for (int i = 0; i < singleResults.size(); i++) { + float providerPriority = searchProviders.get(i).second; + List<PodcastSearchResult> 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<Map.Entry<String, Float>> sortedResults = new ArrayList<>(resultRanking.entrySet()); + Collections.sort(sortedResults, (o1, o2) -> Double.compare(o2.getValue(), o1.getValue())); + + List<PodcastSearchResult> results = new ArrayList<>(); + for (Map.Entry<String, Float> 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<List<PodcastSearchResult>> search(String query) { + return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> { + FyydResponse response = client.searchPodcasts(query, 10) + .subscribeOn(Schedulers.io()) + .blockingGet(); + + ArrayList<PodcastSearchResult> 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<List<PodcastSearchResult>> search(String query) { + return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> { + GpodnetService service = null; + try { + service = new GpodnetService(); + List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0); + List<PodcastSearchResult> 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<List<PodcastSearchResult>> search(String query) { + return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) 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<PodcastSearchResult> podcasts = new ArrayList<>(); + try { + Response response = client.newCall(httpReq.build()).execute(); + + if (response.isSuccessful()) { + String resultString = response.body().string(); + JSONObject result = new JSONObject(resultString); + JSONArray j = result.getJSONArray("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<List<PodcastSearchResult>> loadToplist(int limit) { + return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) 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<PodcastSearchResult> podcasts = parseFeed(feedString); + emitter.onSuccess(podcasts); + } catch (IOException | JSONException e) { + emitter.onError(e); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + } + + public Single<String> getFeedUrl(PodcastSearchResult podcast) { + if (!podcast.feedUrl.contains("itunes.apple.com")) { + return Single.just(podcast.feedUrl) + .observeOn(AndroidSchedulers.mainThread()); + } + return Single.create((SingleOnSubscribe<String>) 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<PodcastSearchResult> parseFeed(String jsonString) throws JSONException { + JSONObject result = new JSONObject(jsonString); + JSONObject feed = result.getJSONObject("feed"); + JSONArray entries = feed.getJSONArray("entry"); + + List<PodcastSearchResult> 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<List<PodcastSearchResult>> 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); + Button butConfirmAddUrl = root.findViewById(R.id.butConfirm); + butConfirmAddUrl.setOnClickListener(v -> { + addUrl(etxtFeedurl.getText().toString()); + }); - final MainActivity activity = (MainActivity) getActivity(); - activity.getSupportActionBar().setTitle(R.string.add_feed_label); + combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox); + combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + performSearch(); + return true; + } + return false; + }); + } - butSearchITunes.setOnClickListener(v -> activity.loadChildFragment(new ItunesSearchFragment())); + private void setupAdvancedSearchButtons(View root) { + View butAdvancedSearch = root.findViewById(R.id.advanced_search); + registerForContextMenu(butAdvancedSearch); + butAdvancedSearch.setOnClickListener(v -> butAdvancedSearch.showContextMenu()); + } - butBrowserGpoddernet.setOnClickListener(v -> activity.loadChildFragment(new GpodnetMainFragment())); + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + getActivity().getMenuInflater().inflate(R.menu.advanced_search, menu); + } - butSearchFyyd.setOnClickListener(v -> activity.loadChildFragment(new FyydSearchFragment())); + @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; + } - 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); - }); + 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); + } - return root; + 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<FeedItem> episodes; - private List<Downloader> downloaderList; - - private boolean itemsLoaded = false; - private boolean viewsCreated = false; + @NonNull + List<FeedItem> episodes = new ArrayList<>(); + @NonNull + private List<Downloader> 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); - - 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; - } + 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) 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); - - recyclerView = root.findViewById(android.R.id.list); - RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator(); - if (animator instanceof SimpleItemAnimator) { - ((SimpleItemAnimator) animator).setSupportsChangeAnimations(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); - progLoading = root.findViewById(R.id.progLoading); - - if (!itemsLoaded) { - progLoading.setVisibility(View.VISIBLE); + RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator(); + if (animator instanceof SimpleItemAnimator) { + ((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); } - viewsCreated = true; - - if (itemsLoaded) { - onFragmentLoaded(); - } + progLoading = root.findViewById(R.id.progLoading); + 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<FeedItem> 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<PodcastSearchResult> 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<FeedItem> items; + private List<FeedItem> 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<DownloadStatus> downloadLog; + private List<DownloadStatus> 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<FeedItem> 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<Podcast> searchResults; + private List<PodcastSearchResult> 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<Downloader> 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); - - 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; - } + FeedMenuHandler.onCreateOptionsMenu(inflater, menu); - @Override - public boolean onQueryTextChange(String s) { - return false; + 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())); } - }); - 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<Podcast> searchResults; - private List<Podcast> topList; + private List<PodcastSearchResult> searchResults; + private List<PodcastSearchResult> 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<Podcast> result) { + private void updateData(List<PodcastSearchResult> 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<String>) 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<List<Podcast>>) 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<Podcast> 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<Podcast> parseFeed(String jsonString) throws JSONException { - JSONObject result = new JSONObject(jsonString); - JSONObject feed = result.getJSONObject("feed"); - JSONArray entries = feed.getJSONArray("entry"); - - List<Podcast> 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<List<Podcast>>) 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<Podcast> 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<FeedItem> 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,23 +47,10 @@ public class PlaybackHistoryFragment extends ListFragment { private List<FeedItem> playbackHistory; private FeedItemlistAdapter adapter; - - private boolean itemsLoaded = false; - private boolean viewsCreated = false; - private List<Downloader> 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); setRetainInstance(true); @@ -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) { - disposable.dispose(); - } - } - - @Override - public void onDetach() { - super.onDetach(); - if(disposable != null) { + 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<PodcastSearchResult> 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<Downloader> downloaderList; + private List<Downloader> 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<SearchResult> searchResults; - - private boolean viewCreated = false; - private boolean itemsLoaded = false; - + private List<SearchResult> 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 @@ -93,21 +90,6 @@ public class SearchFragment extends ListFragment { } @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; - } - - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - MenuItemCompat.setActionView(item, sv); - } + 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); } 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<SearchResult> 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); } + } + @Override + public void onStart() { + super.onStart(); EventDistributor.getInstance().register(contentUpdate); + loadSubscriptions(); } @Override - public void onDestroy() { - super.onDestroy(); + 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 <T> void displayConfirmationDialog(@StringRes int title, @StringRes int message, Callable<? extends T> 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); + } +} |