diff --git a/app/build.gradle b/app/build.gradle
index 5ed8332c4..5c5b3621a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -11,7 +11,7 @@ android {
defaultConfig {
applicationId "protect.card_locker"
- minSdkVersion 15
+ minSdkVersion 16
targetSdkVersion 27
versionCode 35
versionName "0.25.4"
diff --git a/app/src/main/java/protect/card_locker/DBHelper.java b/app/src/main/java/protect/card_locker/DBHelper.java
index e096b16e6..6952accae 100644
--- a/app/src/main/java/protect/card_locker/DBHelper.java
+++ b/app/src/main/java/protect/card_locker/DBHelper.java
@@ -146,16 +146,52 @@ public class DBHelper extends SQLiteOpenHelper
public Cursor getLoyaltyCardCursor()
{
+ // An empty string will match everything
+ return getLoyaltyCardCursor("");
+ }
+
+ /**
+ * Returns a cursor to all loyalty cards with the filter text in either the store or note.
+ *
+ * @param filter
+ * @return Cursor
+ */
+ public Cursor getLoyaltyCardCursor(final String filter)
+ {
+ String actualFilter = String.format("%%%s%%", filter);
+ String[] selectionArgs = { actualFilter, actualFilter };
+
SQLiteDatabase db = getReadableDatabase();
- Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
- " ORDER BY " + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC", null);
+
+ Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
+ " WHERE " + LoyaltyCardDbIds.STORE + " LIKE ? " +
+ " OR " + LoyaltyCardDbIds.NOTE + " LIKE ? " +
+ " ORDER BY " + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC", selectionArgs, null);
return res;
}
public int getLoyaltyCardCount()
{
+ // An empty string will match everything
+ return getLoyaltyCardCount("");
+ }
+
+ /**
+ * Returns the amount of loyalty cards with the filter text in either the store or note.
+ *
+ * @param filter
+ * @return Integer
+ */
+ public int getLoyaltyCardCount(String filter)
+ {
+ String actualFilter = String.format("%%%s%%", filter);
+ String[] selectionArgs = { actualFilter, actualFilter };
+
SQLiteDatabase db = getReadableDatabase();
- Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIds.TABLE, null);
+ Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIds.TABLE +
+ " WHERE " + LoyaltyCardDbIds.STORE + " LIKE ? " +
+ " OR " + LoyaltyCardDbIds.NOTE + " LIKE ? "
+ , selectionArgs, null);
int numItems = 0;
diff --git a/app/src/main/java/protect/card_locker/MainActivity.java b/app/src/main/java/protect/card_locker/MainActivity.java
index 58d4d6b1a..2820fe502 100644
--- a/app/src/main/java/protect/card_locker/MainActivity.java
+++ b/app/src/main/java/protect/card_locker/MainActivity.java
@@ -1,6 +1,8 @@
package protect.card_locker;
+import android.app.SearchManager;
import android.content.ClipData;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ClipboardManager;
@@ -11,6 +13,7 @@ import android.database.Cursor;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.ContextMenu;
@@ -35,6 +38,10 @@ import protect.card_locker.preferences.SettingsActivity;
public class MainActivity extends AppCompatActivity
{
private static final String TAG = "LoyaltyCardLocker";
+ private static final int MAIN_REQUEST_CODE = 1;
+
+ private Menu menu;
+ protected String filter = "";
@Override
protected void onCreate(Bundle savedInstanceState)
@@ -44,7 +51,7 @@ public class MainActivity extends AppCompatActivity
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
- updateLoyaltyCardList();
+ updateLoyaltyCardList("");
SharedPreferences prefs = getSharedPreferences("protect.card_locker", MODE_PRIVATE);
if (prefs.getBoolean("firstrun", true)) {
@@ -54,31 +61,84 @@ public class MainActivity extends AppCompatActivity
}
@Override
- protected void onResume()
- {
+ protected void onResume() {
super.onResume();
- updateLoyaltyCardList();
+ if (menu != null)
+ {
+ SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
+
+ if (!searchView.isIconified()) {
+ filter = searchView.getQuery().toString();
+ }
+ }
+
+ updateLoyaltyCardList(filter);
}
- private void updateLoyaltyCardList()
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == MAIN_REQUEST_CODE)
+ {
+ // We're coming back from another view so clear the search
+ // We only do this now to prevent a flash of all entries right after picking one
+ filter = "";
+ if (menu != null)
+ {
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ searchItem.collapseActionView();
+ }
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (menu == null)
+ {
+ super.onBackPressed();
+ return;
+ }
+
+ SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
+
+ if (!searchView.isIconified()) {
+ searchView.setIconified(true);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ private void updateLoyaltyCardList(String filterText)
{
final ListView cardList = findViewById(R.id.list);
final TextView helpText = findViewById(R.id.helpText);
+ final TextView noMatchingCardsText = findViewById(R.id.noMatchingCardsText);
final DBHelper db = new DBHelper(this);
if(db.getLoyaltyCardCount() > 0)
{
+ // We want the cardList to be visible regardless of the filtered match count
+ // to ensure that the noMatchingCardsText doesn't end up being shown below
+ // the keyboard
cardList.setVisibility(View.VISIBLE);
helpText.setVisibility(View.GONE);
+ if(db.getLoyaltyCardCount(filterText) > 0)
+ {
+ noMatchingCardsText.setVisibility(View.GONE);
+ }
+ else
+ {
+ noMatchingCardsText.setVisibility(View.VISIBLE);
+ }
}
else
{
cardList.setVisibility(View.GONE);
helpText.setVisibility(View.VISIBLE);
+ noMatchingCardsText.setVisibility(View.GONE);
}
- Cursor cardCursor = db.getLoyaltyCardCursor();
+ Cursor cardCursor = db.getLoyaltyCardCursor(filterText);
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
cardList.setAdapter(adapter);
@@ -101,7 +161,7 @@ public class MainActivity extends AppCompatActivity
ShortcutHelper.updateShortcuts(MainActivity.this, loyaltyCard, i);
- startActivity(i);
+ startActivityForResult(i, MAIN_REQUEST_CODE);
}
});
}
@@ -142,7 +202,39 @@ public class MainActivity extends AppCompatActivity
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
+ this.menu = menu;
+
getMenuInflater().inflate(R.menu.main_menu, menu);
+
+ SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ if (searchManager != null) {
+ SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView();
+ searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
+ searchView.setSubmitButtonEnabled(false);
+
+ searchView.setOnCloseListener(new SearchView.OnCloseListener() {
+ @Override
+ public boolean onClose() {
+ invalidateOptionsMenu();
+ return false;
+ }
+ });
+
+ searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return false;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ filter = newText;
+ updateLoyaltyCardList(newText);
+ return true;
+ }
+ });
+ }
+
return super.onCreateOptionsMenu(menu);
}
@@ -154,21 +246,21 @@ public class MainActivity extends AppCompatActivity
if (id == R.id.action_add)
{
Intent i = new Intent(getApplicationContext(), LoyaltyCardEditActivity.class);
- startActivity(i);
+ startActivityForResult(i, MAIN_REQUEST_CODE);
return true;
}
if(id == R.id.action_import_export)
{
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
- startActivity(i);
+ startActivityForResult(i, MAIN_REQUEST_CODE);
return true;
}
if(id == R.id.action_settings)
{
Intent i = new Intent(getApplicationContext(), SettingsActivity.class);
- startActivity(i);
+ startActivityForResult(i, MAIN_REQUEST_CODE);
return true;
}
@@ -276,6 +368,6 @@ public class MainActivity extends AppCompatActivity
private void startIntro()
{
Intent intent = new Intent(this, IntroActivity.class);
- startActivity(intent);
+ startActivityForResult(intent, MAIN_REQUEST_CODE);
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_search_white.png b/app/src/main/res/drawable-hdpi/ic_search_white.png
new file mode 100644
index 000000000..b74d89872
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_search_white.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_search_white.png b/app/src/main/res/drawable-mdpi/ic_search_white.png
new file mode 100644
index 000000000..65709b258
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_search_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_search_white.png b/app/src/main/res/drawable-xhdpi/ic_search_white.png
new file mode 100644
index 000000000..a35ff9349
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_search_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_search_white.png b/app/src/main/res/drawable-xxhdpi/ic_search_white.png
new file mode 100644
index 000000000..0f1207e2c
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_search_white.png differ
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index 56b5d76d1..71eea4380 100644
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -17,6 +17,14 @@
android:text="@string/noGiftCards"
android:visibility="gone"/>
+
+
+
-
Loyalty Card Keychain
+ Search
Add
You don\'t have any loyalty cards at the moment. Click the "+" (plus) button up top to get started.\n\nLoyalty Card Locker lets you carry your loyalty cards on your phone, so they are always within reach.
+ No loyalty cards match the search filter. Please try some different terms.
Store
Note
diff --git a/app/src/test/java/protect/card_locker/MainActivityTest.java b/app/src/test/java/protect/card_locker/MainActivityTest.java
index a74790049..5523ad61d 100644
--- a/app/src/test/java/protect/card_locker/MainActivityTest.java
+++ b/app/src/test/java/protect/card_locker/MainActivityTest.java
@@ -8,6 +8,7 @@ import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Color;
import android.os.Bundle;
+import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.View;
import android.widget.ListView;
@@ -53,6 +54,9 @@ public class MainActivityTest
TextView helpText = activity.findViewById(R.id.helpText);
assertEquals(View.VISIBLE, helpText.getVisibility());
+ TextView noMatchingCardsText = activity.findViewById(R.id.noMatchingCardsText);
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+
ListView list = activity.findViewById(R.id.list);
assertEquals(View.GONE, list.getVisibility());
}
@@ -65,9 +69,10 @@ public class MainActivityTest
final Menu menu = shadowOf(activity).getOptionsMenu();
assertTrue(menu != null);
- // The settings and add button should be present
- assertEquals(menu.size(), 5);
+ // The settings, search and add button should be present
+ assertEquals(menu.size(), 6);
+ assertEquals("Search", menu.findItem(R.id.action_search).getTitle().toString());
assertEquals("Add", menu.findItem(R.id.action_add).getTitle().toString());
assertEquals("Import/Export", menu.findItem(R.id.action_import_export).getTitle().toString());
assertEquals("Start Intro", menu.findItem(R.id.action_intro).getTitle().toString());
@@ -98,6 +103,7 @@ public class MainActivityTest
activityController.resume();
TextView helpText = mainActivity.findViewById(R.id.helpText);
+ TextView noMatchingCardsText = mainActivity.findViewById(R.id.noMatchingCardsText);
ListView list = mainActivity.findViewById(R.id.list);
assertEquals(0, list.getCount());
@@ -106,12 +112,14 @@ public class MainActivityTest
db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE);
assertEquals(View.VISIBLE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.GONE, list.getVisibility());
activityController.pause();
activityController.resume();
assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(1, list.getAdapter().getCount());
@@ -119,6 +127,99 @@ public class MainActivityTest
assertNotNull(cursor);
}
+ @Test
+ public void testFiltering()
+ {
+ ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
+
+ MainActivity mainActivity = (MainActivity)activityController.get();
+ activityController.start();
+ activityController.resume();
+
+ TextView helpText = mainActivity.findViewById(R.id.helpText);
+ TextView noMatchingCardsText = mainActivity.findViewById(R.id.noMatchingCardsText);
+ ListView list = mainActivity.findViewById(R.id.list);
+
+ DBHelper db = new DBHelper(mainActivity);
+ db.insertLoyaltyCard("The First Store", "Initial note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE);
+ db.insertLoyaltyCard("The Second Store", "Secondary note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE);
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(2, list.getCount());
+
+ mainActivity.filter = "store";
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(2, list.getCount());
+
+ mainActivity.filter = "first";
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
+ mainActivity.filter = "initial";
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
+ mainActivity.filter = "second";
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
+ mainActivity.filter = "company";
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.VISIBLE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(0, list.getCount());
+
+ mainActivity.filter = "";
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(2, list.getCount());
+ }
+
@Test
public void testFirstRunStartsIntro()
{