diff --git a/app/src/main/java/org/openlp/android2/dialogs/SearchSelectionDialog.java b/app/src/main/java/org/openlp/android2/dialogs/SearchSelectionDialog.java new file mode 100644 index 0000000..2f76220 --- /dev/null +++ b/app/src/main/java/org/openlp/android2/dialogs/SearchSelectionDialog.java @@ -0,0 +1,133 @@ +/****************************************************************************** + * OpenLP - Open Source Lyrics Projection * + * --------------------------------------------------------------------------- * + * Copyright (c) 2011-2015 OpenLP Android Developers * + * --------------------------------------------------------------------------- * + * This program 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; version 2 of the License. * + * * + * This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 * + * Temple Place, Suite 330, Boston, MA 02111-1307 USA * + *******************************************************************************/ +package org.openlp.android2.dialogs; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.RadioButton; +import android.widget.Toast; +import org.openlp.android2.R; +import org.openlp.android2.api.Api; +import org.openlp.android2.common.JsonHelpers; +import org.openlp.android2.common.OpenLPDialog; + +public class SearchSelectionDialog extends OpenLPDialog { + private final String LOG_TAG = SearchSelectionDialog.class.getName(); + public AlertDialog dialog; + private String key; + private String plugin; + private String text; + private RadioButton sendLive; + private RadioButton addToService; + + /** + * The system calls this only when creating the layout in a dialog. + */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // The only reason you might override this method when using onCreateView() is + // to modify any dialog characteristics. For example, the dialog includes a + // title by default, but your custom layout might not need it. So here you can + // remove the dialog title, but you must call the superclass to get the Dialog. + + key = getArguments().getString("key"); + plugin = getArguments().getString("plugin"); + text = getArguments().getString("text"); + + context = getActivity(); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + // Get the layout inflater + LayoutInflater inflater = getActivity().getLayoutInflater(); + + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + View view = inflater.inflate(R.layout.search_action_dialog, null); + builder.setView(view); + + sendLive = (RadioButton) view.findViewById(R.id.buttonLive); + sendLive.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + createLive(); + SearchSelectionDialog.this.getDialog().cancel(); + } + }); + + addToService = (RadioButton) view.findViewById(R.id.buttonService); + addToService.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + createService(); + SearchSelectionDialog.this.getDialog().cancel(); + } + }); + + builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + SearchSelectionDialog.this.getDialog().cancel(); + } + }); + dialog = builder.create(); + dialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialogI) { + Button btnNegative = dialog.getButton(Dialog.BUTTON_NEGATIVE); + btnNegative.setTextSize(20); + } + }); + return dialog; + } + + @Override + public void onResume() { + super.onResume(); + Log.d(LOG_TAG, "Resuming..."); + } + + public void createLive() { + try { + String request = JsonHelpers.createRequestJSON("id", key); + String url = String.format(Api.SEARCH_PLUGIN_LIVE, plugin.toLowerCase()); + triggerTextRequest(String.format("%s%s", url, request)); + Log.d(LOG_TAG, String.format("Setting list data. apiBase(%s), text(%s)", Api.SEARCH_PLUGIN_LIVE, text)); + } catch (JsonHelpers.JSONHandlerException e) { + e.printStackTrace(); + Toast.makeText(context, "Request Failed", Toast.LENGTH_SHORT).show(); + } + } + + public void createService() { + try { + String request = JsonHelpers.createRequestJSON("id", key); + String url = String.format(Api.SEARCH_PLUGIN_ADD, plugin.toLowerCase()); + triggerTextRequest(String.format("%s%s", url, request)); + Log.d(LOG_TAG, String.format("Setting list data. apiBase(%s), text(%s)", Api.SEARCH_PLUGIN_ADD, text)); + } catch (JsonHelpers.JSONHandlerException e) { + e.printStackTrace(); + Toast.makeText(context, "Request Failed", Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/app/src/main/java/org/openlp/android2/fragments/SearchFragment.java b/app/src/main/java/org/openlp/android2/fragments/SearchFragment.java new file mode 100644 index 0000000..c8d9962 --- /dev/null +++ b/app/src/main/java/org/openlp/android2/fragments/SearchFragment.java @@ -0,0 +1,346 @@ +/****************************************************************************** + * OpenLP - Open Source Lyrics Projection * + * --------------------------------------------------------------------------- * + * Copyright (c) 2011-2016 OpenLP Android Developers * + * --------------------------------------------------------------------------- * + * This program 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; version 2 of the License. * + * * + * This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 * + * Temple Place, Suite 330, Boston, MA 02111-1307 USA * + *******************************************************************************/ +package org.openlp.android2.fragments; + +import android.app.DialogFragment; +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.volley.AuthFailureError; +import com.android.volley.ClientError; +import com.android.volley.DefaultRetryPolicy; +import com.android.volley.NetworkError; +import com.android.volley.NoConnectionError; +import com.android.volley.ParseError; +import com.android.volley.Request; +import com.android.volley.Response; +import com.android.volley.ServerError; +import com.android.volley.TimeoutError; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.StringRequest; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.openlp.android2.R; +import org.openlp.android2.api.Api; +import org.openlp.android2.common.JsonHelpers; + +import org.openlp.android2.common.RequestQueueService; +import org.openlp.android2.dialogs.SearchSelectionDialog; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + + */ +public class SearchFragment extends Fragment { + + private final String LOG_TAG = SearchFragment.class.getName(); + private Spinner spinner; + public Context context; + protected String calledURL; + protected String updateUrl; + protected String searchedPlugin; + protected Map pluginMap = new HashMap(); + protected ArrayList jsonCache = new ArrayList(); + + public SearchFragment() { + Log.d(LOG_TAG, "Constructor"); + } + + public static SearchFragment newInstance() { + SearchFragment fragment = new SearchFragment(); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + context = getActivity(); + updateUrl = Api.SEARCHABLE_PLUGINS; + View view = inflater.inflate(R.layout.fragment_search, container, false); + spinner = (Spinner) view.findViewById(R.id.search_spinner); + triggerTextRequest(Api.SEARCHABLE_PLUGINS); + + // Add search listener to text field + EditText editText = (EditText) view.findViewById(R.id.search_text); + editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView tv, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + // Now close the keyboard as finished with + View view = getActivity().getCurrentFocus(); + if (view != null) { + InputMethodManager imm = + (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + searchedPlugin = pluginMap.get(spinner.getSelectedItem().toString()); + requestSearch(tv.getText().toString()); + return true; + } + return false; + } + }); + return view; + } + + @Override + public void onDetach() { + super.onDetach(); + } + + private void populatePluginList(String response, Boolean notInError) { + Log.i(LOG_TAG, "populatePluginList - entry"); + List categories = new ArrayList(); + pluginMap.clear(); + + if (notInError) { + try { + JSONArray items = new JSONObject(response).getJSONObject("results").getJSONArray("items"); + for (int i = 0; i < items.length(); ++i) { + JSONArray item = items.getJSONArray(i); + categories.add(item.get(1).toString()); + pluginMap.put(item.get(1).toString(), item.get(0).toString()); + } + } catch (JSONException e) { + Log.e(LOG_TAG, response); + e.printStackTrace(); + } + ArrayAdapter LTRadapter = new ArrayAdapter(getActivity(), + R.layout.spinner_list_item, categories); + LTRadapter.setDropDownViewResource(R.layout.spinner_dropdown_item); + spinner.setAdapter(LTRadapter); + Log.i(LOG_TAG, "populatePluginList - exit"); + } + } + + protected void triggerTextRequest(String urlbase) { + Log.d(LOG_TAG, "Trigger Request for url " + urlbase); + String url = RequestQueueService.getInstance(this.context).getUrl(urlbase); + calledURL = urlbase; + + StringRequest request = new StringRequest( + Request.Method.GET, + url, + listener, + errorListener) { + + @Override + public Map getHeaders() throws AuthFailureError { + return createBasicAuthHeader("user", "passwd"); + } + }; + //Set a retry policy in case of SocketTimeout & ConnectionTimeout Exceptions. + // Volley does retry for you if you have specified the policy. + request.setRetryPolicy(new DefaultRetryPolicy( + RequestQueueService.getInstance(this.context).getConnectionTimeout(), + DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); + request.setTag("OpenLP"); + RequestQueueService.getInstance(this.context).addToRequestQueue(request); + } + + Map createBasicAuthHeader(String username, String password) { + Map headers = new HashMap(); + headers.put("Authorization", RequestQueueService.getInstance(context).getBasicAuth()); + + return headers; + } + + Response.Listener listener = new Response.Listener() { + @Override + public void onResponse(String response) { + if (calledURL.equals(updateUrl)) { + populatePluginList(response, true); + } else { + populateListDisplay(response, true); + } + } + }; + + Response.ErrorListener errorListener = new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + Log.d(LOG_TAG, String.format("Call response error = %s", error.toString())); + if (error instanceof NetworkError) { + } else if (error instanceof ClientError) { + } else if (error instanceof ServerError) { + } else if (error instanceof AuthFailureError) { + Toast.makeText(context, R.string.httpreturn_unauthorised, + Toast.LENGTH_LONG).show(); + } else if (error instanceof ParseError) { + } else if (error instanceof NoConnectionError) { + } else if (error instanceof TimeoutError) { + } + Toast.makeText(context, R.string.unable, + Toast.LENGTH_LONG).show(); + + } + }; + + public void requestSearch(String text) { + updateUrl = Api.SEARCH_PLUGIN_FORMATTED; + try { + String request = JsonHelpers.createRequestJSON("text", text); + String url = String.format(Api.SEARCH_PLUGIN_FORMATTED, searchedPlugin); + triggerTextRequest(String.format("%s%s", url, request)); + Log.d(LOG_TAG, String.format("Search request. apiBase(%s), text(%s)", searchedPlugin, text)); + } catch (JsonHelpers.JSONHandlerException e) { + e.printStackTrace(); + Toast.makeText(context, "Search Request Failed", Toast.LENGTH_SHORT).show(); + } + } + + public void populateListDisplay(String json, boolean notInError) { + Log.i(LOG_TAG, "populateListDisplay - entry"); + ListView list = (ListView) getActivity().findViewById(R.id.searchlistView); + final ArrayList listitems = new ArrayList(); + if (notInError) { + try { + JSONArray items = new JSONObject(json).getJSONObject("results").getJSONArray("items"); + for (int i = 0; i < items.length(); ++i) { + JSONArray item = items.getJSONArray(i); + listitems.add(item); + } + } catch (JSONException e) { + Log.e(LOG_TAG, json); + e.printStackTrace(); + } + } + + jsonCache = new ArrayList(); + final StableArrayAdapter adapter = new StableArrayAdapter(context, + android.R.layout.simple_list_item_1, + listitems, + jsonCache); + + list.setAdapter(adapter); + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + + @Override + public void onItemClick(AdapterView parent, final View view, + int position, long id) { + final JSONArray item = (JSONArray) parent.getItemAtPosition(position); + //Toast.makeText(context, "Item Pressed " + String.valueOf(position) + item, + // Toast.LENGTH_SHORT).show(); + String it = ""; + try { + it = (String) item.get(1); + } catch (JSONException e) { + e.printStackTrace(); + } + Bundle args = new Bundle(); + args.putString("plugin", searchedPlugin); + args.putString("text", it); + args.putString("key", Long.toString(id)); + DialogFragment newFragment = new SearchSelectionDialog(); + newFragment.setArguments(args); + newFragment.show(getFragmentManager(), "TAG"); + + } + }); + Log.i(LOG_TAG, "populateListDisplay - exit"); + } + + private class StableArrayAdapter extends ArrayAdapter { + + HashMap mIdMap = new HashMap(); + + public StableArrayAdapter(Context context, + int textViewResourceId, + List objects, + ArrayList jsonCache) { + super(context, textViewResourceId, objects); + + for (int i = 0; i < objects.size(); ++i) { + JSONArray item = objects.get(i); + try { + jsonCache.add(item); + mIdMap.put(item.get(1).toString(), i); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + // Get the data item for this position + //User user = getItem(position); + String item = null; + try { + item = getItem(position).get(1).toString(); + } catch (JSONException e) { + e.printStackTrace(); + } + // Check if an existing view is being reused, otherwise inflate the view + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(R.layout.search_result_row, + parent, false); + } + // Lookup view for data population + TextView tvItem = (TextView) convertView.findViewById(R.id.searchListRow); + // Populate the data into the template view using the data object + tvItem.setText(item); + // Return the completed view to render on screen + return convertView; + } + + @Override + public long getItemId(int position) { + String item = null; + try { + item = getItem(position).get(1).toString(); + } catch (JSONException e) { + e.printStackTrace(); + } + return mIdMap.get(item); + } + + @Override + public boolean hasStableIds() { + return true; + } + + } + +} diff --git a/app/src/main/res/drawable-hdpi/ic_search_black.png b/app/src/main/res/drawable-hdpi/ic_search_black.png new file mode 100644 index 0000000..3ae490e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_search_black.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_search_black.png b/app/src/main/res/drawable-mdpi/ic_search_black.png new file mode 100644 index 0000000..6381902 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_search_black.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_search_black.png b/app/src/main/res/drawable-xhdpi/ic_search_black.png new file mode 100644 index 0000000..21be572 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_search_black.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_search_black.png b/app/src/main/res/drawable-xxhdpi/ic_search_black.png new file mode 100644 index 0000000..a5e7a9c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_search_black.png differ diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml new file mode 100644 index 0000000..591aed8 --- /dev/null +++ b/app/src/main/res/layout/fragment_search.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_action_dialog.xml b/app/src/main/res/layout/search_action_dialog.xml new file mode 100644 index 0000000..b210efb --- /dev/null +++ b/app/src/main/res/layout/search_action_dialog.xml @@ -0,0 +1,40 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_result_row.xml b/app/src/main/res/layout/search_result_row.xml new file mode 100644 index 0000000..105f166 --- /dev/null +++ b/app/src/main/res/layout/search_result_row.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/spinner_dropdown_item.xml b/app/src/main/res/layout/spinner_dropdown_item.xml new file mode 100644 index 0000000..42fcee9 --- /dev/null +++ b/app/src/main/res/layout/spinner_dropdown_item.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/spinner_list_item.xml b/app/src/main/res/layout/spinner_list_item.xml new file mode 100644 index 0000000..d76b839 --- /dev/null +++ b/app/src/main/res/layout/spinner_list_item.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/fixssl.iml b/fixssl.iml new file mode 100644 index 0000000..9b1c62e --- /dev/null +++ b/fixssl.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file