Android: Searching

This commit is contained in:
jendib 2014-11-23 00:49:56 +01:00
parent c610364ef7
commit 1773998ca0
16 changed files with 253 additions and 56 deletions

View File

@ -102,6 +102,7 @@
<orderEntry type="library" exported="" name="support-annotations-21.0.2" level="project" /> <orderEntry type="library" exported="" name="support-annotations-21.0.2" level="project" />
<orderEntry type="library" exported="" name="imagezoom-1.0.5" level="project" /> <orderEntry type="library" exported="" name="imagezoom-1.0.5" level="project" />
<orderEntry type="library" exported="" name="support-v4-21.0.2" level="project" /> <orderEntry type="library" exported="" name="support-v4-21.0.2" level="project" />
<orderEntry type="library" exported="" name="eventbus-2.4.0" level="project" />
<orderEntry type="library" exported="" name="android-query.0.26.8" level="project" /> <orderEntry type="library" exported="" name="android-query.0.26.8" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-21.0.2" level="project" /> <orderEntry type="library" exported="" name="appcompat-v7-21.0.2" level="project" />
<orderEntry type="library" exported="" name="android-async-http-1.4.6" level="project" /> <orderEntry type="library" exported="" name="android-async-http-1.4.6" level="project" />

View File

@ -35,4 +35,5 @@ dependencies {
compile 'com.android.support:recyclerview-v7:21.0.0' compile 'com.android.support:recyclerview-v7:21.0.0'
compile 'com.loopj.android:android-async-http:1.4.6' compile 'com.loopj.android:android-async-http:1.4.6'
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5' compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
compile 'de.greenrobot:eventbus:2.4.0'
} }

View File

@ -27,12 +27,19 @@
android:label="@string/app_name" android:label="@string/app_name"
android:logo="@drawable/ic_launcher" android:logo="@drawable/ic_launcher"
android:launchMode="singleTop"> android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
</activity> </activity>
<activity <activity
android:name=".activity.DocumentActivity" android:name=".activity.DocumentActivity"
android:label="" android:label=""
android:logo="@drawable/ic_launcher"> android:logo="@drawable/ic_launcher">
</activity> </activity>
<provider android:name=".provider.RecentSuggestionsProvider"
android:exported="false"
android:authorities="com.sismics.docs.provider.RecentSuggestionsProvider" />
</application> </application>
</manifest> </manifest>

View File

@ -23,7 +23,6 @@ public class MainApplication extends Application {
// TODO Fullscreen preview // TODO Fullscreen preview
// TODO Sharing // TODO Sharing
// TODO Error feedback // TODO Error feedback
// TODO Searching
// TODO Printing // TODO Printing
super.onCreate(); super.onCreate();

View File

@ -1,18 +1,28 @@
package com.sismics.docs.activity; package com.sismics.docs.activity;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.provider.SearchRecentSuggestions;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.widget.TextView;
import android.widget.AdapterView;
import android.widget.ListView;
import com.sismics.docs.R; import com.sismics.docs.R;
import com.sismics.docs.event.SearchEvent;
import com.sismics.docs.model.application.ApplicationContext; import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.provider.RecentSuggestionsProvider;
import org.json.JSONObject;
import de.greenrobot.event.EventBus;
/** /**
* Main activity. * Main activity.
@ -21,8 +31,8 @@ import com.sismics.docs.model.application.ApplicationContext;
*/ */
public class MainActivity extends ActionBarActivity { public class MainActivity extends ActionBarActivity {
private ListView drawerList;
private ActionBarDrawerToggle drawerToggle; private ActionBarDrawerToggle drawerToggle;
private MenuItem searchItem;
@Override @Override
protected void onCreate(final Bundle args) { protected void onCreate(final Bundle args) {
@ -38,30 +48,27 @@ public class MainActivity extends ActionBarActivity {
// Setup the activity // Setup the activity
setContentView(R.layout.main_activity); setContentView(R.layout.main_activity);
// Cache view references // Enable ActionBar app icon to behave as action to toggle nav drawer
DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
drawerList = (ListView) findViewById(R.id.drawer_list); if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// Drawer item click listener getSupportActionBar().setHomeButtonEnabled(true);
drawerList.setOnItemClickListener(new ListView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
}
});
if (drawerLayout != null) {
// Enable ActionBar app icon to behave as action to toggle nav drawer
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
// ActionBarDrawerToggle ties together the the proper interactions
// between the sliding drawer and the action bar app icon
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout,
R.string.drawer_open, R.string.drawer_close);
drawerLayout.setDrawerListener(drawerToggle);
} }
// ActionBarDrawerToggle ties together the the proper interactions
// between the sliding drawer and the action bar app icon
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout,
R.string.drawer_open, R.string.drawer_close);
drawerLayout.setDrawerListener(drawerToggle);
// Fill the drawer user info
JSONObject userInfo = ApplicationContext.getInstance().getUserInfo();
TextView usernameTextView = (TextView) findViewById(R.id.usernameTextView);
usernameTextView.setText(userInfo.optString("username"));
TextView emailTextView = (TextView) findViewById(R.id.emailTextView);
emailTextView.setText(userInfo.optString("email"));
handleIntent(getIntent());
} }
@Override @Override
@ -70,7 +77,7 @@ public class MainActivity extends ActionBarActivity {
case android.R.id.home: case android.R.id.home:
// The action bar home/up action should open or close the drawer. // The action bar home/up action should open or close the drawer.
// ActionBarDrawerToggle will take care of this. // ActionBarDrawerToggle will take care of this.
if (drawerToggle != null && drawerToggle.onOptionsItemSelected(item)) { if (drawerToggle.onOptionsItemSelected(item)) {
return true; return true;
} }
return true; return true;
@ -83,24 +90,69 @@ public class MainActivity extends ActionBarActivity {
@Override @Override
protected void onPostCreate(Bundle savedInstanceState) { protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState); super.onPostCreate(savedInstanceState);
if (drawerToggle != null) { // Sync the toggle state after onRestoreInstanceState has occurred.
// Sync the toggle state after onRestoreInstanceState has occurred. drawerToggle.syncState();
drawerToggle.syncState();
}
} }
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
if (drawerToggle != null) { // Pass any configuration change to the drawer toggle
// Pass any configuration change to the drawer toggle drawerToggle.onConfigurationChanged(newConfig);
drawerToggle.onConfigurationChanged(newConfig);
}
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { public boolean onCreateOptionsMenu(Menu menu) {
super.onSaveInstanceState(outState); MenuInflater inflater = getMenuInflater();
outState.putInt("drawerItemSelected", drawerList.getCheckedItemPosition()); inflater.inflate(R.menu.main_activity, menu);
// Get the SearchView and set the searchable configuration
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setOnCloseListener(new SearchView.OnCloseListener() {
@Override
public boolean onClose() {
EventBus.getDefault().post(new SearchEvent(null));
return false;
}
});
return super.onCreateOptionsMenu(menu);
}
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
handleIntent(intent);
}
/**
* Handle the incoming intent.
*
* @param intent Intent
*/
private void handleIntent(Intent intent) {
// Intent is consumed
setIntent(null);
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
// Perform a search query
String query = intent.getStringExtra(SearchManager.QUERY);
// Collapse the SearchView
if (searchItem != null) {
searchItem.collapseActionView();
}
// Save the query
SearchRecentSuggestions suggestions = new SearchRecentSuggestions(this, RecentSuggestionsProvider.AUTHORITY, RecentSuggestionsProvider.MODE);
suggestions.saveRecentQuery(query, null);
EventBus.getDefault().post(new SearchEvent(query));
}
} }
} }

View File

@ -88,14 +88,21 @@ public class DocListAdapter extends RecyclerView.Adapter<DocListAdapter.ViewHold
return documents.optJSONObject(position); return documents.optJSONObject(position);
} }
/**
* Clear the documents.
*/
public void clearDocuments() {
documents = new JSONArray();
notifyDataSetChanged();
}
/** /**
* Add documents to display. * Add documents to display.
* *
* @param documents Documents * @param documents Documents
* @param reset Reset the list
*/ */
public void addDocuments(JSONArray documents, boolean reset) { public void addDocuments(JSONArray documents) {
if (this.documents == null || reset) { if (this.documents == null) {
this.documents = new JSONArray(); this.documents = new JSONArray();
} }

View File

@ -0,0 +1,31 @@
package com.sismics.docs.event;
/**
* Search event.
*
* @author bgamard.
*/
public class SearchEvent {
/**
* Search query.
*/
private String query;
/**
* Create a search event.
*
* @param query Query
*/
public SearchEvent(String query) {
this.query = query;
}
/**
* Getter of query.
*
* @return query
*/
public String getQuery() {
return query;
}
}

View File

@ -14,12 +14,15 @@ import com.sismics.docs.DividerItemDecoration;
import com.sismics.docs.R; import com.sismics.docs.R;
import com.sismics.docs.activity.DocumentActivity; import com.sismics.docs.activity.DocumentActivity;
import com.sismics.docs.adapter.DocListAdapter; import com.sismics.docs.adapter.DocListAdapter;
import com.sismics.docs.event.SearchEvent;
import com.sismics.docs.listener.RecyclerItemClickListener; import com.sismics.docs.listener.RecyclerItemClickListener;
import com.sismics.docs.resource.DocumentResource; import com.sismics.docs.resource.DocumentResource;
import org.apache.http.Header; import org.apache.http.Header;
import org.json.JSONObject; import org.json.JSONObject;
import de.greenrobot.event.EventBus;
/** /**
* @author bgamard. * @author bgamard.
*/ */
@ -27,12 +30,16 @@ public class DocListFragment extends Fragment {
/** /**
* Documents adapter. * Documents adapter.
*/ */
DocListAdapter adapter; private DocListAdapter adapter;
/**
* Search query.
*/
private String query;
// Infinite scrolling things // Infinite scrolling things
private boolean loading = true; private boolean loading = true;
private int previousTotal = 0; private int previousTotal = 0;
int firstVisibleItem, visibleItemCount, totalItemCount;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -69,9 +76,9 @@ public class DocListFragment extends Fragment {
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy); super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount(); int visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount(); int totalItemCount = layoutManager.getItemCount();
firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
if (loading) { if (loading) {
if (totalItemCount > previousTotal) { if (totalItemCount > previousTotal) {
@ -80,26 +87,54 @@ public class DocListFragment extends Fragment {
} }
} }
if (!loading && totalItemCount - visibleItemCount <= firstVisibleItem + 3) { if (!loading && totalItemCount - visibleItemCount <= firstVisibleItem + 3) {
loadDocuments(); loadDocuments(false);
loading = true; loading = true;
} }
} }
}); });
// Grab the documents // Grab the documents
loadDocuments(); loadDocuments(true);
EventBus.getDefault().register(this);
return view; return view;
} }
@Override
public void onDestroyView() {
EventBus.getDefault().unregister(this);
super.onDestroyView();
}
/**
* A search event has been fired.
*
* @param event Search event
*/
public void onEvent(SearchEvent event) {
query = event.getQuery();
loadDocuments(true);
}
/** /**
* Refresh the document list. * Refresh the document list.
*
* @param reset If true, reload the documents
*/ */
private void loadDocuments() { private void loadDocuments(final boolean reset) {
DocumentResource.list(getActivity(), adapter.getItemCount(), new JsonHttpResponseHandler() { if (reset) {
loading = true;
previousTotal = 0;
adapter.clearDocuments();
if (getView() != null) {
getView().findViewById(R.id.progressBar).setVisibility(View.VISIBLE);
}
}
DocumentResource.list(getActivity(), reset ? 0 : adapter.getItemCount(), query, new JsonHttpResponseHandler() {
@Override @Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) { public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
adapter.addDocuments(response.optJSONArray("documents"), false); adapter.addDocuments(response.optJSONArray("documents"));
if (getView() != null) { if (getView() != null) {
getView().findViewById(R.id.progressBar).setVisibility(View.GONE); getView().findViewById(R.id.progressBar).setVisibility(View.GONE);
} }

View File

@ -0,0 +1,17 @@
package com.sismics.docs.provider;
import android.content.SearchRecentSuggestionsProvider;
/**
* Search recent suggestions provider.
*
* @author bgamard.
*/
public class RecentSuggestionsProvider extends SearchRecentSuggestionsProvider {
public final static String AUTHORITY = "com.sismics.docs.provider.RecentSuggestionsProvider";
public final static int MODE = DATABASE_MODE_QUERIES;
public RecentSuggestionsProvider() {
setupSuggestions(AUTHORITY, MODE);
}
}

View File

@ -15,9 +15,11 @@ public class DocumentResource extends BaseResource {
* GET /document/list. * GET /document/list.
* *
* @param context Context * @param context Context
* @param offset Offset
* @param query Search query
* @param responseHandler Callback * @param responseHandler Callback
*/ */
public static void list(Context context, int offset, JsonHttpResponseHandler responseHandler) { public static void list(Context context, int offset, String query, JsonHttpResponseHandler responseHandler) {
init(context); init(context);
RequestParams params = new RequestParams(); RequestParams params = new RequestParams();
@ -25,6 +27,7 @@ public class DocumentResource extends BaseResource {
params.put("offset", offset); params.put("offset", offset);
params.put("sort_column", 3); params.put("sort_column", 3);
params.put("asc", false); params.put("asc", false);
params.put("search", query);
client.get(getApiUrl(context) + "/document/list", params, responseHandler); client.get(getApiUrl(context) + "/document/list", params, responseHandler);
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -18,12 +18,40 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start" android:layout_gravity="start"
android:gravity="center" android:gravity="center"
android:orientation="vertical"> android:orientation="vertical"
android:background="#fff"
android:elevation="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#aaa"
android:padding="12dp"
android:orientation="vertical"
android:elevation="4dp">
<TextView
android:id="@+id/usernameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:fontFamily="sans-serif"/>
<TextView
android:id="@+id/emailTextView"
android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:fontFamily="sans-serif-light"/>
</LinearLayout>
<ListView <ListView
android:id="@+id/drawer_list" android:layout_weight="1"
android:id="@+id/tagListView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:choiceMode="singleChoice" android:choiceMode="singleChoice"
android:divider="@android:color/transparent" android:divider="@android:color/transparent"
android:dividerHeight="0dp" /> android:dividerHeight="0dp" />

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_search"
android:title="@string/action_search"
app:showAsAction="ifRoom"
app:actionViewClass="android.support.v7.widget.SearchView" />
</menu>

View File

@ -30,5 +30,6 @@
<string name="downloading_file">Downloading file number %1s</string> <string name="downloading_file">Downloading file number %1s</string>
<string name="download_document">Download all files</string> <string name="download_document">Download all files</string>
<string name="downloading_document">Downloading document</string> <string name="downloading_document">Downloading document</string>
<string name="action_search">Search documents</string>
</resources> </resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/app_name"
android:hint="@string/action_search"
android:searchSuggestAuthority="com.sismics.docs.provider.RecentSuggestionsProvider"
android:searchSuggestSelection=" ?">
</searchable>