diff --git a/app/app.iml b/app/app.iml index 78d7daa..b5521ed 100644 --- a/app/app.iml +++ b/app/app.iml @@ -1,5 +1,5 @@ - + @@ -65,27 +65,20 @@ - - - - - - - - - + + @@ -97,4 +90,209 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/openlp/android2/OpenLP.java b/app/src/main/java/org/openlp/android2/OpenLP.java index 5312d6b..5a4e2f1 100644 --- a/app/src/main/java/org/openlp/android2/OpenLP.java +++ b/app/src/main/java/org/openlp/android2/OpenLP.java @@ -42,6 +42,7 @@ import org.openlp.android2.fragments.HomeFragment; import org.openlp.android2.fragments.LiveListFragment; import org.openlp.android2.fragments.LiveWebFragment; import org.openlp.android2.fragments.NavigationDrawerFragment; +import org.openlp.android2.fragments.SearchFragment; import org.openlp.android2.fragments.ServiceListFragment; import org.openlp.android2.fragments.StageWebFragment; @@ -163,6 +164,24 @@ public class OpenLP extends ActionBarActivity toggerContainer(R.id.next_button, View.GONE); toggerContainer(R.id.prev_button, View.GONE); break; + case NavigationOptions.Search: + singleTab(); + fragmentManager.beginTransaction() + .replace(R.id.container, SearchFragment.newInstance()) + .commit(); + mTitle = getString(R.string.action_search); + toggerContainer(R.id.next_button, View.GONE); + toggerContainer(R.id.prev_button, View.GONE); + break; + default: + singleTab(); + fragmentManager.beginTransaction() + .replace(R.id.container, HomeFragment.newInstance()) + .commit(); + mTitle = getString(R.string.home); + toggerContainer(R.id.next_button, View.GONE); + toggerContainer(R.id.prev_button, View.GONE); + break; } } diff --git a/app/src/main/java/org/openlp/android2/common/NavigationOptions.java b/app/src/main/java/org/openlp/android2/common/NavigationOptions.java index b231c3c..e2726b4 100644 --- a/app/src/main/java/org/openlp/android2/common/NavigationOptions.java +++ b/app/src/main/java/org/openlp/android2/common/NavigationOptions.java @@ -24,4 +24,5 @@ public class NavigationOptions{ public final static int LiveList = 2; public final static int StageView = 3; public final static int LiveView = 4; + public final static int Search = 5; } diff --git a/app/src/main/java/org/openlp/android2/common/OpenLPDialog.java b/app/src/main/java/org/openlp/android2/common/OpenLPDialog.java index 02f9d64..d9a889b 100644 --- a/app/src/main/java/org/openlp/android2/common/OpenLPDialog.java +++ b/app/src/main/java/org/openlp/android2/common/OpenLPDialog.java @@ -45,7 +45,8 @@ abstract public class OpenLPDialog extends DialogFragment { protected void triggerTextRequest(String url) { calledURL = url; Log.d(LOG_TAG, "Trigger Request for url " + url); - String callurl = String.format("%s%s", httpClient.getAbsoluteUrl(client), url ); + String callurl = String.format("%s%s", httpClient.getAbsoluteUrl(client), url); + client.get(callurl, null, new TextHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, String responseString) { diff --git a/app/src/main/java/org/openlp/android2/common/OpenLPHttpClient.java b/app/src/main/java/org/openlp/android2/common/OpenLPHttpClient.java index e1384a4..1927cbc 100644 --- a/app/src/main/java/org/openlp/android2/common/OpenLPHttpClient.java +++ b/app/src/main/java/org/openlp/android2/common/OpenLPHttpClient.java @@ -76,7 +76,7 @@ public class OpenLPHttpClient { client.setSSLSocketFactory(sf); } catch (Exception e){ - // + Log.d(LOG_TAG, "Unable to support SSL"); } } return urlBase; 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..454bf6b --- /dev/null +++ b/app/src/main/java/org/openlp/android2/dialogs/SearchSelectionDialog.java @@ -0,0 +1,135 @@ +/****************************************************************************** + * 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; +import org.openlp.android2.common.OpenLPHttpClient; + +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(); + httpClient = new OpenLPHttpClient(context); + + 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/NavigationDrawerFragment.java b/app/src/main/java/org/openlp/android2/fragments/NavigationDrawerFragment.java index 6024cf6..7f920b2 100644 --- a/app/src/main/java/org/openlp/android2/fragments/NavigationDrawerFragment.java +++ b/app/src/main/java/org/openlp/android2/fragments/NavigationDrawerFragment.java @@ -42,7 +42,6 @@ import android.widget.AdapterView; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.SimpleAdapter; -import android.widget.Toast; import org.openlp.android2.R; @@ -152,6 +151,11 @@ public class NavigationDrawerFragment extends Fragment { hm4.put("icon", Integer.toString(R.drawable.ic_ondemand_video_black)); aList.add(hm4); + HashMap hm5 = new HashMap(); + hm5.put("title", getString(R.string.action_search)); + hm5.put("icon", Integer.toString(R.drawable.ic_search_black)); + aList.add(hm5); + // Keys used in Hashmap String[] from = {"icon", "title"}; 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..8a097bd --- /dev/null +++ b/app/src/main/java/org/openlp/android2/fragments/SearchFragment.java @@ -0,0 +1,283 @@ +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.loopj.android.http.AsyncHttpClient; +import com.loopj.android.http.TextHttpResponseHandler; + +import org.apache.http.Header; +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.OpenLPHttpClient; +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; + private static AsyncHttpClient client = new AsyncHttpClient(); + public Context context; + protected String calledURL; + protected OpenLPHttpClient httpClient; + protected String updateUrl; + protected String searchedPlugin; + protected Map pluginMap = new HashMap(); + + 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; + httpClient = new OpenLPHttpClient(context); + 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(); + } + + public void manageResponse(String response, boolean notInError) { + if (calledURL.equals(updateUrl)) { + populatePluginList(response, notInError); + } else { + populateListDisplay(response, notInError); + } + } + + 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 url) { + calledURL = url; + Log.d(LOG_TAG, "Trigger Request for url " + url); + String callurl = String.format("%s%s", httpClient.getAbsoluteUrl(client), url ); + client.get(callurl, null, new TextHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, String responseString) { + // called when response HTTP status is "200 OK" + manageResponse(responseString, true); + } + + @Override + public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) { + // called when response HTTP status is "4XX" (eg. 401, 403, 404) + if (statusCode == 401) { + Toast.makeText(context, R.string.httpreturn_unauthorised, Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(context, R.string.unable, Toast.LENGTH_LONG).show(); + } + manageResponse(responseString, false); + } + }); + } + + 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(); + } + } + + final StableArrayAdapter adapter = new StableArrayAdapter(context, + android.R.layout.simple_list_item_1, listitems); + + 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) { + super(context, textViewResourceId, objects); + for (int i = 0; i < objects.size(); ++i) { + JSONArray item = objects.get(i); + try { + mIdMap.put(item.get(1).toString(), Integer.valueOf(item.get(0).toString())); + } 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.xml b/app/src/main/res/layout/search_result.xml new file mode 100644 index 0000000..684cb15 --- /dev/null +++ b/app/src/main/res/layout/search_result.xml @@ -0,0 +1,11 @@ + + + + + \ 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/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df5a823..0d6bc72 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,14 +29,14 @@ Select the required display Display Desktop background Allow the selected item to scroll to the centre of the list - Show Live display + Live display Display Setting Display Black only Display Theme only Enable Custom Timeouts Enter Alert Text Allow displays to auto center - Home + Home Unauthorised Access, please enter valid username and password Live List Live View @@ -48,6 +48,10 @@ Previous Process Service List + Search Results + Send Live + Add to Service + Showing Results for \'%s\' Stage View Select display text size Change the Service text size