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() {