Migrate to fragments and improvements

- Migrate to fragments
- Fix #487
- Don't show "Open in popup mode" to channel links
- New backstack of videos
- Change the subscribers count to format using `NumberFormat`, for example some locales use `.`  others `,`, this handles it automatically (and the old method had a bug for leading zero, e.g. 4.82.125 instead of 4.082.125)
- Add string 'subscribers' for channels with more than 1 subscriber (plural)
- Popup player chooses the default format and resolution based on the new preference (format)
- Fix taskaffinity of the router activities
- Show title before loading, as it is available from the items already loaded
This commit is contained in:
Mauricio Colli 2017-04-09 14:34:00 -03:00
parent 9318bb5306
commit 746c2a15bf
39 changed files with 2026 additions and 1506 deletions

View file

@ -0,0 +1,10 @@
package org.schabi.newpipe.fragments;
import org.schabi.newpipe.extractor.StreamingService;
/**
* Interface for communication purposes between activity and fragment
*/
public interface OnItemSelectedListener {
void onItemSelected(StreamingService.LinkType linkType, int serviceId, String url, String name);
}

View file

@ -0,0 +1,416 @@
package org.schabi.newpipe.fragments.channel;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
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.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.ImageErrorLoadingListener;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.workers.ChannelExtractorWorker;
import java.text.NumberFormat;
import static android.os.Build.VERSION.SDK_INT;
/**
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ChannelFragment.java is part of NewPipe.
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class ChannelFragment extends Fragment implements ChannelExtractorWorker.OnChannelInfoReceive {
private static final String TAG = "ChannelFragment";
private AppCompatActivity activity;
private OnItemSelectedListener onItemSelectedListener;
private InfoListAdapter infoListAdapter;
private ChannelExtractorWorker currentExtractorWorker;
private ChannelInfo currentChannelInfo;
private int serviceId = -1;
private String channelName = "";
private String channelUrl = "";
private boolean isLoading = false;
private int pageNumber = 0;
private boolean hasNextPage = true;
private ImageLoader imageLoader = ImageLoader.getInstance();
/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private View rootView = null;
private RecyclerView channelVideosList;
private LinearLayoutManager layoutManager;
private ProgressBar loadingProgressBar;
private View headerRootLayout;
private ImageView headerChannelBanner;
private ImageView headerAvatarView;
private TextView headerTitleView;
private TextView headerSubscriberView;
private Button headerSubscriberButton;
private View headerSubscriberLayout;
/*////////////////////////////////////////////////////////////////////////*/
public ChannelFragment() {
}
public static ChannelFragment newInstance(int serviceId, String url, String name) {
ChannelFragment instance = newInstance();
Bundle bundle = new Bundle();
bundle.putString(Constants.KEY_URL, url);
bundle.putString(Constants.KEY_TITLE, name);
bundle.putInt(Constants.KEY_SERVICE_ID, serviceId);
instance.setArguments(bundle);
return instance;
}
public static ChannelFragment newInstance() {
return new ChannelFragment();
}
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
activity = ((AppCompatActivity) context);
onItemSelectedListener = ((OnItemSelectedListener) context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
isLoading = false;
if (savedInstanceState != null) {
channelUrl = savedInstanceState.getString(Constants.KEY_URL);
channelName = savedInstanceState.getString(Constants.KEY_TITLE);
serviceId = savedInstanceState.getInt(Constants.KEY_SERVICE_ID, -1);
} else {
try {
Bundle args = getArguments();
if (args != null) {
channelUrl = args.getString(Constants.KEY_URL);
channelName = args.getString(Constants.KEY_TITLE);
serviceId = args.getInt(Constants.KEY_SERVICE_ID, 0);
}
} catch (Exception e) {
e.printStackTrace();
ErrorActivity.reportError(getActivity(), e, null,
getActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(serviceId),
"", R.string.general_error));
}
}
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_channel, container, false);
return rootView;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
loadingProgressBar = (ProgressBar) view.findViewById(R.id.loading_progress_bar);
channelVideosList = (RecyclerView) view.findViewById(R.id.channel_streams_view);
infoListAdapter = new InfoListAdapter(activity, rootView);
layoutManager = new LinearLayoutManager(activity);
channelVideosList.setLayoutManager(layoutManager);
headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, channelVideosList, false);
infoListAdapter.setHeader(headerRootLayout);
infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, channelVideosList, false));
channelVideosList.setAdapter(infoListAdapter);
headerChannelBanner = (ImageView) headerRootLayout.findViewById(R.id.channel_banner_image);
headerAvatarView = (ImageView) headerRootLayout.findViewById(R.id.channel_avatar_view);
headerTitleView = (TextView) headerRootLayout.findViewById(R.id.channel_title_view);
headerSubscriberView = (TextView) headerRootLayout.findViewById(R.id.channel_subscriber_view);
headerSubscriberButton = (Button) headerRootLayout.findViewById(R.id.channel_subscribe_button);
headerSubscriberLayout = headerRootLayout.findViewById(R.id.channel_subscriber_layout);
initListeners();
isLoading = true;
}
@Override
public void onDestroyView() {
super.onDestroyView();
headerAvatarView.setImageBitmap(null);
headerChannelBanner.setImageBitmap(null);
channelVideosList.removeAllViews();
rootView = null;
channelVideosList = null;
layoutManager = null;
loadingProgressBar = null;
headerRootLayout = null;
headerChannelBanner = null;
headerAvatarView = null;
headerTitleView = null;
headerSubscriberView = null;
headerSubscriberButton = null;
headerSubscriberLayout = null;
}
@Override
public void onResume() {
super.onResume();
if (isLoading) {
requestData(false);
}
}
@Override
public void onStop() {
super.onStop();
if (currentExtractorWorker != null) currentExtractorWorker.cancel();
}
@Override
public void onDestroy() {
super.onDestroy();
imageLoader.clearMemoryCache();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(Constants.KEY_URL, channelUrl);
outState.putString(Constants.KEY_TITLE, channelName);
outState.putInt(Constants.KEY_SERVICE_ID, serviceId);
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.menu_channel, menu);
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayShowTitleEnabled(true);
supportActionBar.setDisplayHomeAsUpEnabled(true);
//noinspection deprecation
supportActionBar.setNavigationMode(0);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_item_openInBrowser: {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(channelUrl));
startActivity(Intent.createChooser(intent, getString(R.string.choose_browser)));
return true;
}
case R.id.menu_item_share:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, channelUrl);
intent.setType("text/plain");
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/*//////////////////////////////////////////////////////////////////////////
// Init's
//////////////////////////////////////////////////////////////////////////*/
private void initListeners() {
infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
}
});
// detect if list has ben scrolled to the bottom
channelVideosList.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int pastVisiblesItems, visibleItemCount, totalItemCount;
super.onScrolled(recyclerView, dx, dy);
//check for scroll down
if (dy > 0) {
visibleItemCount = layoutManager.getChildCount();
totalItemCount = layoutManager.getItemCount();
pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !currentExtractorWorker.isRunning() && hasNextPage) {
pageNumber++;
requestData(true);
}
}
}
});
headerSubscriberButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, currentChannelInfo.feed_url);
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(currentChannelInfo.feed_url));
startActivity(i);
}
});
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private String buildSubscriberString(long count) {
String out = NumberFormat.getNumberInstance().format(count);
out += " " + getString(count > 1 ? R.string.subscriber_plural : R.string.subscriber);
return out;
}
private void requestData(boolean onlyVideos) {
if (currentExtractorWorker != null && currentExtractorWorker.isRunning()) currentExtractorWorker.cancel();
isLoading = true;
if (!onlyVideos) {
//delete already displayed content
loadingProgressBar.setVisibility(View.VISIBLE);
infoListAdapter.clearSteamItemList();
pageNumber = 0;
headerSubscriberLayout.setVisibility(View.GONE);
headerTitleView.setText(channelName != null ? channelName : "");
if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(channelName != null ? channelName : "");
if (SDK_INT >= 21) {
headerChannelBanner.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.channel_banner));
headerAvatarView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
}
infoListAdapter.showFooter(false);
}
currentExtractorWorker = new ChannelExtractorWorker(activity, serviceId, channelUrl, pageNumber, this);
currentExtractorWorker.setOnlyVideos(onlyVideos);
currentExtractorWorker.start();
}
private void addVideos(ChannelInfo info) {
infoListAdapter.addInfoItemList(info.related_streams);
}
/*//////////////////////////////////////////////////////////////////////////
// OnChannelInfoReceiveListener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onReceive(ChannelInfo info) {
if (info == null || isRemoving() || !isVisible()) return;
currentChannelInfo = info;
if (!currentExtractorWorker.isOnlyVideos()) {
headerRootLayout.setVisibility(View.VISIBLE);
loadingProgressBar.setVisibility(View.GONE);
if (info.channel_name != null && !info.channel_name.isEmpty()) {
if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(info.channel_name);
headerTitleView.setText(info.channel_name);
channelName = info.channel_name;
} else channelName = "";
if (info.banner_url != null && !info.banner_url.isEmpty()) {
imageLoader.displayImage(info.banner_url, headerChannelBanner,
new ImageErrorLoadingListener(activity, rootView, info.service_id));
}
if (info.avatar_url != null && !info.avatar_url.isEmpty()) {
headerAvatarView.setVisibility(View.VISIBLE);
imageLoader.displayImage(info.avatar_url, headerAvatarView,
new ImageErrorLoadingListener(activity, rootView, info.service_id));
}
if (info.subscriberCount != -1) {
headerSubscriberView.setText(buildSubscriberString(info.subscriberCount));
}
if ((info.feed_url != null && !info.feed_url.isEmpty()) || (info.subscriberCount != -1)) {
headerSubscriberLayout.setVisibility(View.VISIBLE);
}
if (info.feed_url == null || info.feed_url.isEmpty()) {
headerSubscriberButton.setVisibility(View.INVISIBLE);
}
infoListAdapter.showFooter(true);
}
hasNextPage = info.hasNextPage;
if (!hasNextPage) infoListAdapter.showFooter(false);
addVideos(info);
isLoading = false;
}
@Override
public void onError(int messageId) {
Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
}
}

View file

@ -0,0 +1,213 @@
package org.schabi.newpipe.fragments.detail;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.util.Utils;
import java.util.List;
/**
* Created by Christian Schabesberger on 18.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* DetailsMenuHandler.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
class ActionBarHandler {
private static final String TAG = ActionBarHandler.class.toString();
private AppCompatActivity activity;
private int selectedVideoStream = -1;
private SharedPreferences defaultPreferences;
private Menu menu;
// Only callbacks are listed here, there are more actions which don't need a callback.
// those are edited directly. Typically VideoDetailFragment will implement those callbacks.
private OnActionListener onShareListener;
private OnActionListener onOpenInBrowserListener;
private OnActionListener onOpenInPopupListener;
private OnActionListener onDownloadListener;
private OnActionListener onPlayWithKodiListener;
private OnActionListener onPlayAudioListener;
// Triggered when a stream related action is triggered.
public interface OnActionListener {
void onActionSelected(int selectedStreamId);
}
public ActionBarHandler(AppCompatActivity activity) {
this.activity = activity;
}
@SuppressWarnings({"deprecation", "ConstantConditions"})
public void setupNavMenu(AppCompatActivity activity) {
this.activity = activity;
try {
activity.getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
} catch (NullPointerException e) {
e.printStackTrace();
}
}
public void setupStreamList(final List<VideoStream> videoStreams) {
if (activity != null) {
selectedVideoStream = 0;
// this array will be shown in the dropdown menu for selecting the stream/resolution.
String[] itemArray = new String[videoStreams.size()];
for (int i = 0; i < videoStreams.size(); i++) {
VideoStream item = videoStreams.get(i);
itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution;
}
int defaultResolution = Utils.getPreferredResolution(activity, videoStreams);
ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(activity.getBaseContext(),
android.R.layout.simple_spinner_dropdown_item, itemArray);
ActionBar ab = activity.getSupportActionBar();
//todo: make this throwsable
assert ab != null : "Could not get actionbar";
ab.setListNavigationCallbacks(itemAdapter
, new ActionBar.OnNavigationListener() {
@Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
selectedVideoStream = (int) itemId;
return true;
}
});
ab.setSelectedNavigationItem(defaultResolution);
}
}
public void setupMenu(Menu menu, MenuInflater inflater) {
this.menu = menu;
// CAUTION set item properties programmatically otherwise it would not be accepted by
// appcompat itemsinflater.inflate(R.menu.videoitem_detail, menu);
defaultPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
inflater.inflate(R.menu.videoitem_detail, menu);
showPlayWithKodiAction(defaultPreferences
.getBoolean(activity.getString(R.string.show_play_with_kodi_key), false));
}
public boolean onItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.menu_item_share: {
/*
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, websiteUrl);
intent.setType("text/plain");
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
*/
if(onShareListener != null) {
onShareListener.onActionSelected(selectedVideoStream);
}
return true;
}
case R.id.menu_item_openInBrowser: {
if(onOpenInBrowserListener != null) {
onOpenInBrowserListener.onActionSelected(selectedVideoStream);
}
}
return true;
case R.id.menu_item_download:
if(onDownloadListener != null) {
onDownloadListener.onActionSelected(selectedVideoStream);
}
return true;
case R.id.action_play_with_kodi:
if(onPlayWithKodiListener != null) {
onPlayWithKodiListener.onActionSelected(selectedVideoStream);
}
return true;
case R.id.menu_item_play_audio:
if(onPlayAudioListener != null) {
onPlayAudioListener.onActionSelected(selectedVideoStream);
}
return true;
case R.id.menu_item_popup: {
if(onOpenInPopupListener != null) {
onOpenInPopupListener.onActionSelected(selectedVideoStream);
}
return true;
}
default:
Log.e(TAG, "Menu Item not known");
}
return false;
}
public int getSelectedVideoStream() {
return selectedVideoStream;
}
public void setOnShareListener(OnActionListener listener) {
onShareListener = listener;
}
public void setOnOpenInBrowserListener(OnActionListener listener) {
onOpenInBrowserListener = listener;
}
public void setOnOpenInPopupListener(OnActionListener listener) {
onOpenInPopupListener = listener;
}
public void setOnDownloadListener(OnActionListener listener) {
onDownloadListener = listener;
}
public void setOnPlayWithKodiListener(OnActionListener listener) {
onPlayWithKodiListener = listener;
}
public void setOnPlayAudioListener(OnActionListener listener) {
onPlayAudioListener = listener;
}
public void showAudioAction(boolean visible) {
menu.findItem(R.id.menu_item_play_audio).setVisible(visible);
}
public void showDownloadAction(boolean visible) {
menu.findItem(R.id.menu_item_download).setVisible(visible);
}
public void showPlayWithKodiAction(boolean visible) {
menu.findItem(R.id.action_play_with_kodi).setVisible(visible);
}
}

View file

@ -0,0 +1,31 @@
package org.schabi.newpipe.fragments.detail;
import java.io.Serializable;
@SuppressWarnings("WeakerAccess")
public class StackItem implements Serializable {
private String title, url;
public StackItem(String url, String title) {
this.title = title;
this.url = url;
}
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public String getUrl() {
return url;
}
@Override
public String toString() {
return getUrl() + " > " + getTitle();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,440 @@
package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
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.view.inputmethod.InputMethodManager;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.EnumSet;
import static android.app.Activity.RESULT_OK;
import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
/**
* Created by Christian Schabesberger on 02.08.16.
* <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SearchFragment.java is part of NewPipe.
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SearchFragment extends Fragment implements SearchView.OnQueryTextListener, SearchWorker.SearchWorkerResultListener {
private static final String TAG = SearchFragment.class.toString();
// savedInstanceBundle arguments
private static final String QUERY = "query";
private static final String STREAMING_SERVICE = "streaming_service";
private int streamingServiceId = -1;
private String searchQuery = "";
private boolean isLoading = false;
@SuppressWarnings("FieldCanBeLocal")
private SearchView searchView;
private RecyclerView recyclerView;
private ProgressBar loadingIndicator;
private int pageNumber = 0;
private SuggestionListAdapter suggestionListAdapter;
private InfoListAdapter infoListAdapter;
private LinearLayoutManager streamInfoListLayoutManager;
private EnumSet<SearchEngine.Filter> filter = EnumSet.of(SearchEngine.Filter.CHANNEL, SearchEngine.Filter.STREAM);
private OnItemSelectedListener onItemSelectedListener;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public SearchFragment() {
}
@SuppressWarnings("unused")
public static SearchFragment newInstance(int streamingServiceId, String searchQuery) {
Bundle args = new Bundle();
args.putInt(STREAMING_SERVICE, streamingServiceId);
args.putString(QUERY, searchQuery);
SearchFragment fragment = new SearchFragment();
fragment.setArguments(args);
return fragment;
}
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
onItemSelectedListener = ((OnItemSelectedListener) context);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
searchQuery = "";
isLoading = false;
if (savedInstanceState != null) {
searchQuery = savedInstanceState.getString(QUERY);
streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
} else {
try {
Bundle args = getArguments();
if (args != null) {
searchQuery = args.getString(QUERY);
streamingServiceId = args.getInt(STREAMING_SERVICE);
} else {
streamingServiceId = NewPipe.getIdOfService("Youtube");
}
} catch (Exception e) {
e.printStackTrace();
ErrorActivity.reportError(getActivity(), e, null,
getActivity().findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(streamingServiceId),
"", R.string.general_error));
}
}
setHasOptionsMenu(true);
SearchWorker sw = SearchWorker.getInstance();
sw.setSearchWorkerResultListener(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_search, container, false);
Context context = view.getContext();
loadingIndicator = (ProgressBar) view.findViewById(R.id.loading_progress_bar);
recyclerView = (RecyclerView) view.findViewById(R.id.list);
streamInfoListLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(streamInfoListLayoutManager);
infoListAdapter = new InfoListAdapter(getActivity(),
getActivity().findViewById(android.R.id.content));
infoListAdapter.setFooter(inflater.inflate(R.layout.pignate_footer, recyclerView, false));
infoListAdapter.showFooter(false);
infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
}
});
infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
public void selected(int serviceId, String url, String title) {
NavigationHelper.openChannel(onItemSelectedListener, serviceId, url, title);
}
});
recyclerView.setAdapter(infoListAdapter);
recyclerView.clearOnScrollListeners();
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int pastVisiblesItems, visibleItemCount, totalItemCount;
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) //check for scroll down
{
visibleItemCount = streamInfoListLayoutManager.getChildCount();
totalItemCount = streamInfoListLayoutManager.getItemCount();
pastVisiblesItems = streamInfoListLayoutManager.findFirstVisibleItemPosition();
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading) {
pageNumber++;
recyclerView.post(new Runnable() {
@Override
public void run() {
infoListAdapter.showFooter(true);
}
});
search(searchQuery, pageNumber);
}
}
}
});
return view;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (!searchQuery.isEmpty()) {
search(searchQuery);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
recyclerView.removeAllViews();
infoListAdapter.clearSteamItemList();
recyclerView = null;
}
@Override
public void onResume() {
super.onResume();
if (isLoading && !searchQuery.isEmpty()) {
search(searchQuery);
}
}
@Override
public void onStop() {
super.onStop();
SearchWorker.getInstance().terminate();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(QUERY, searchQuery);
outState.putInt(STREAMING_SERVICE, streamingServiceId);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case RECAPTCHA_REQUEST:
if (resultCode == RESULT_OK && searchQuery.length() != 0) {
search(searchQuery);
} else Log.e(TAG, "ReCaptcha failed");
break;
default:
Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
break;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
ActionBar supportActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(false);
supportActionBar.setDisplayShowTitleEnabled(false);
//noinspection deprecation
supportActionBar.setNavigationMode(0);
}
inflater.inflate(R.menu.search_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
searchView = (SearchView) searchItem.getActionView();
setupSearchView(searchView);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_filter_all:
changeFilter(item, EnumSet.of(SearchEngine.Filter.STREAM, SearchEngine.Filter.CHANNEL));
return true;
case R.id.menu_filter_video:
changeFilter(item, EnumSet.of(SearchEngine.Filter.STREAM));
return true;
case R.id.menu_filter_channel:
changeFilter(item, EnumSet.of(SearchEngine.Filter.CHANNEL));
return true;
default:
return false;
}
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void changeFilter(MenuItem item, EnumSet<SearchEngine.Filter> filter) {
this.filter = filter;
item.setChecked(true);
if (searchQuery != null && !searchQuery.isEmpty()) {
Log.e(TAG, "Fuck+ " + searchQuery);
search(searchQuery);
}
}
private void setupSearchView(SearchView searchView) {
suggestionListAdapter = new SuggestionListAdapter(getActivity());
searchView.setSuggestionsAdapter(suggestionListAdapter);
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter));
searchView.setOnQueryTextListener(this);
if (searchQuery != null && !searchQuery.isEmpty()) {
searchView.setQuery(searchQuery, false);
searchView.setIconifiedByDefault(false);
}
}
private void search(String query) {
infoListAdapter.clearSteamItemList();
infoListAdapter.showFooter(false);
pageNumber = 0;
searchQuery = query;
search(query, pageNumber);
hideBackground();
loadingIndicator.setVisibility(View.VISIBLE);
}
private void search(String query, int page) {
isLoading = true;
SearchWorker sw = SearchWorker.getInstance();
sw.search(streamingServiceId,
query,
page,
getActivity(),
filter);
}
private void setDoneLoading() {
isLoading = false;
loadingIndicator.setVisibility(View.GONE);
infoListAdapter.showFooter(false);
}
/**
* Hides the "dummy" background when no results are shown
*/
private void hideBackground() {
View view = getView();
if (view == null) return;
view.findViewById(R.id.mainBG).setVisibility(View.GONE);
}
private void searchSuggestions(String query) {
SuggestionSearchRunnable suggestionSearchRunnable =
new SuggestionSearchRunnable(streamingServiceId, query, getActivity(), suggestionListAdapter);
Thread suggestionThread = new Thread(suggestionSearchRunnable);
suggestionThread.start();
}
public boolean isMainBgVisible() {
return getActivity().findViewById(R.id.mainBG).getVisibility() == View.VISIBLE;
}
/*//////////////////////////////////////////////////////////////////////////
// OnQueryTextListener
//////////////////////////////////////////////////////////////////////////*/
@Override
public boolean onQueryTextSubmit(String query) {
Activity a = getActivity();
try {
search(query);
// hide virtual keyboard
InputMethodManager inputManager =
(InputMethodManager) a.getSystemService(Context.INPUT_METHOD_SERVICE);
try {
//noinspection ConstantConditions
inputManager.hideSoftInputFromWindow(
a.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
} catch (NullPointerException e) {
e.printStackTrace();
ErrorActivity.reportError(a, e, null,
a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(streamingServiceId),
"Could not get widget with focus", R.string.general_error));
}
// clear focus
// 1. to not open up the keyboard after switching back to this
// 2. It's a workaround to a seeming bug by the Android OS it self, causing
// onQueryTextSubmit to trigger twice when focus is not cleared.
// See: http://stackoverflow.com/questions/17874951/searchview-onquerytextsubmit-runs-twice-while-i-pressed-once
a.getCurrentFocus().clearFocus();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
if (!newText.isEmpty()) {
searchSuggestions(newText);
}
return true;
}
/*//////////////////////////////////////////////////////////////////////////
// SearchWorkerResultListener
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onResult(SearchResult result) {
infoListAdapter.addInfoItemList(result.resultList);
setDoneLoading();
}
@Override
public void onNothingFound(int stringResource) {
//setListShown(true);
Toast.makeText(getActivity(), getString(stringResource), Toast.LENGTH_SHORT).show();
setDoneLoading();
}
@Override
public void onError(String message) {
//setListShown(true);
Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
setDoneLoading();
}
@Override
public void onReCaptchaChallenge() {
Toast.makeText(getActivity(), "ReCaptcha Challenge requested",
Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
startActivityForResult(new Intent(getActivity(), ReCaptchaActivity.class), RECAPTCHA_REQUEST);
}
}

View file

@ -0,0 +1,49 @@
package org.schabi.newpipe.fragments.search;
import android.support.v7.widget.SearchView;
/**
* Created by Christian Schabesberger on 02.08.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SearchSuggestionListener.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SearchSuggestionListener implements SearchView.OnSuggestionListener{
private final SearchView searchView;
private final SuggestionListAdapter adapter;
public SearchSuggestionListener(SearchView searchView, SuggestionListAdapter adapter) {
this.searchView = searchView;
this.adapter = adapter;
}
@Override
public boolean onSuggestionSelect(int position) {
String suggestion = adapter.getSuggestion(position);
searchView.setQuery(suggestion,true);
return false;
}
@Override
public boolean onSuggestionClick(int position) {
String suggestion = adapter.getSuggestion(position);
searchView.setQuery(suggestion,true);
return false;
}
}

View file

@ -0,0 +1,216 @@
package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.report.ErrorActivity;
import java.io.IOException;
import java.util.EnumSet;
/**
* Created by Christian Schabesberger on 02.08.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SearchWorker.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SearchWorker {
private static final String TAG = SearchWorker.class.toString();
public interface SearchWorkerResultListener {
void onResult(SearchResult result);
void onNothingFound(final int stringResource);
void onError(String message);
void onReCaptchaChallenge();
}
private class ResultRunnable implements Runnable {
private final SearchResult result;
private int requestId = 0;
public ResultRunnable(SearchResult result, int requestId) {
this.result = result;
this.requestId = requestId;
}
@Override
public void run() {
if(this.requestId == SearchWorker.this.requestId) {
searchWorkerResultListener.onResult(result);
}
}
}
private class SearchRunnable implements Runnable {
public static final String YOUTUBE = "Youtube";
private final String query;
private final int page;
private final EnumSet<SearchEngine.Filter> filter;
final Handler h = new Handler();
private volatile boolean runs = true;
private Activity a = null;
private int serviceId = -1;
public SearchRunnable(int serviceId,
String query,
int page,
EnumSet<SearchEngine.Filter> filter,
Activity activity,
int requestId) {
this.serviceId = serviceId;
this.query = query;
this.page = page;
this.filter = filter;
this.a = activity;
}
void terminate() {
runs = false;
}
@Override
public void run() {
final String serviceName = NewPipe.getNameOfService(serviceId);
SearchResult result = null;
SearchEngine engine = null;
try {
engine = NewPipe.getService(serviceId)
.getSearchEngineInstance();
} catch(ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
Integer.toString(serviceId), query, R.string.general_error));
return;
}
try {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
String searchLanguageKey = a.getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
a.getString(R.string.default_language_value));
result = SearchResult
.getSearchResult(engine, query, page, searchLanguage, filter);
if(runs) {
h.post(new ResultRunnable(result, requestId));
}
// look for errors during extraction
// soft errors:
View rootView = a.findViewById(android.R.id.content);
if(result != null &&
!result.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
for(Throwable e : result.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
if(result.resultList.isEmpty()&& !result.errors.isEmpty()) {
// if it compleatly failes dont show snackbar, instead show error directlry
ErrorActivity.reportError(h, a, result.errors, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
serviceName, query, R.string.parsing_error));
} else {
// if it partly show snackbar
ErrorActivity.reportError(h, a, result.errors, null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
serviceName, query, R.string.light_parsing_error));
}
}
// hard errors:
} catch (ReCaptchaException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListener.onReCaptchaChallenge();
}
});
} catch(IOException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListener.onNothingFound(R.string.network_error);
}
});
e.printStackTrace();
} catch(final SearchEngine.NothingFoundException e) {
h.post(new Runnable() {
@Override
public void run() {
searchWorkerResultListener.onError(e.getMessage());
}
});
} catch(ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
serviceName, query, R.string.parsing_error));
//postNewErrorToast(h, R.string.parsing_error);
e.printStackTrace();
} catch(Exception e) {
ErrorActivity.reportError(h, a, e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.general_error));
e.printStackTrace();
}
}
}
private static SearchWorker searchWorker = null;
private SearchWorkerResultListener searchWorkerResultListener = null;
private SearchRunnable runnable = null;
private int requestId = 0; //prevents running requests that have already ben expired
public static SearchWorker getInstance() {
return searchWorker == null ? (searchWorker = new SearchWorker()) : searchWorker;
}
public void setSearchWorkerResultListener(SearchWorkerResultListener listener) {
searchWorkerResultListener = listener;
}
private SearchWorker() {
}
public void search(int serviceId,
String query,
int page,
Activity a,
EnumSet<SearchEngine.Filter> filter) {
if(runnable != null) {
terminate();
}
runnable = new SearchRunnable(serviceId, query, page, filter, a, requestId);
Thread thread = new Thread(runnable);
thread.start();
}
public void terminate() {
if (runnable == null) return;
requestId++;
runnable.terminate();
}
}

View file

@ -0,0 +1,84 @@
package org.schabi.newpipe.fragments.search;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.support.v4.widget.ResourceCursorAdapter;
import android.view.View;
import android.widget.TextView;
import java.util.List;
/**
* Created by Christian Schabesberger on 02.08.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SuggestionListAdapter.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* {@link ResourceCursorAdapter} to display suggestions.
*/
public class SuggestionListAdapter extends ResourceCursorAdapter {
private static final String[] columns = new String[]{"_id", "title"};
private static final int INDEX_ID = 0;
private static final int INDEX_TITLE = 1;
public SuggestionListAdapter(Context context) {
super(context, android.R.layout.simple_list_item_1, null, 0);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder viewHolder = new ViewHolder(view);
viewHolder.suggestionTitle.setText(cursor.getString(INDEX_TITLE));
}
/**
* Update the suggestion list
* @param suggestions the list of suggestions
*/
public void updateAdapter(List<String> suggestions) {
MatrixCursor cursor = new MatrixCursor(columns, suggestions.size());
int i = 0;
for (String suggestion : suggestions) {
String[] columnValues = new String[columns.length];
columnValues[INDEX_TITLE] = suggestion;
columnValues[INDEX_ID] = Integer.toString(i);
cursor.addRow(columnValues);
i++;
}
changeCursor(cursor);
}
/**
* Get the suggestion for a position
* @param position the position of the suggestion
* @return the suggestion
*/
public String getSuggestion(int position) {
return ((Cursor) getItem(position)).getString(INDEX_TITLE);
}
private class ViewHolder {
private final TextView suggestionTitle;
private ViewHolder(View view) {
this.suggestionTitle = (TextView) view.findViewById(android.R.id.text1);
}
}
}

View file

@ -0,0 +1,105 @@
package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.SuggestionExtractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.report.ErrorActivity;
import java.io.IOException;
import java.util.List;
/**
* Created by Christian Schabesberger on 02.08.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SuggestionSearchRunnable.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SuggestionSearchRunnable implements Runnable{
/**
* Runnable to update a {@link SuggestionListAdapter}
*/
private class SuggestionResultRunnable implements Runnable{
private final List<String> suggestions;
private SuggestionResultRunnable(List<String> suggestions) {
this.suggestions = suggestions;
}
@Override
public void run() {
adapter.updateAdapter(suggestions);
}
}
private final int serviceId;
private final String query;
private final Handler h = new Handler();
private final Activity a;
private final SuggestionListAdapter adapter;
public SuggestionSearchRunnable(int serviceId, String query,
Activity activity, SuggestionListAdapter adapter) {
this.serviceId = serviceId;
this.query = query;
this.a = activity;
this.adapter = adapter;
}
@Override
public void run() {
try {
SuggestionExtractor se =
NewPipe.getService(serviceId).getSuggestionExtractorInstance();
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
String searchLanguageKey = a.getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
a.getString(R.string.default_language_value));
List<String> suggestions = se.suggestionList(query, searchLanguage);
h.post(new SuggestionResultRunnable(suggestions));
} catch (ExtractionException e) {
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(serviceId), query, R.string.parsing_error));
e.printStackTrace();
} catch (IOException e) {
postNewErrorToast(h, R.string.network_error);
e.printStackTrace();
} catch (Exception e) {
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
NewPipe.getNameOfService(serviceId), query, R.string.general_error));
}
}
private void postNewErrorToast(Handler h, final int stringResource) {
h.post(new Runnable() {
@Override
public void run() {
Toast.makeText(a, a.getString(stringResource),
Toast.LENGTH_SHORT).show();
}
});
}
}