diff --git a/app/build.gradle b/app/build.gradle
index 73e10bf37..ba2f7c362 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -45,7 +45,7 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'androidx.appcompat:appcompat:1.2.0'
- compile 'com.google.android.material:material:1.2.0-alpha03'
+ compile 'com.google.android.material:material:1.2.1'
compile 'androidx.legacy:legacy-support-v4:1.0.0'
compile 'com.journeyapps:zxing-android-embedded:3.5.0@aar'
compile 'com.google.zxing:core:3.3.0'
@@ -58,9 +58,6 @@ dependencies {
implementation 'androidx.cardview:cardview:1.0.0'
testCompile 'junit:junit:4.12'
testCompile "org.robolectric:robolectric:4.0.2"
-
-
-
}
task findbugs(type: FindBugs, dependsOn: 'assembleDebug') {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 54d70c2d6..82a908124 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -33,6 +33,11 @@
+
+
cardGroups = helper.getLoyaltyCardGroups(cardId);
+ cardGroups.add(helper.getGroup(groupId));
+ helper.setLoyaltyCardGroups(database, cardId, cardGroups);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/protect/card_locker/DBHelper.java b/app/src/main/java/protect/card_locker/DBHelper.java
index 2bf42a606..66acad49d 100644
--- a/app/src/main/java/protect/card_locker/DBHelper.java
+++ b/app/src/main/java/protect/card_locker/DBHelper.java
@@ -1,18 +1,26 @@
package protect.card_locker;
-
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import java.util.ArrayList;
+import java.util.List;
+
public class DBHelper extends SQLiteOpenHelper
{
public static final String DATABASE_NAME = "Catima.db";
public static final int ORIGINAL_DATABASE_VERSION = 1;
public static final int DATABASE_VERSION = 4;
+ static class LoyaltyCardDbGroups
+ {
+ public static final String TABLE = "groups";
+ public static final String ID = "_id";
+ }
+
static class LoyaltyCardDbIds
{
public static final String TABLE = "cards";
@@ -26,6 +34,13 @@ public class DBHelper extends SQLiteOpenHelper
public static final String STAR_STATUS = "starstatus";
}
+ static class LoyaltyCardDbIdsGroups
+ {
+ public static final String TABLE = "cardsGroups";
+ public static final String cardID = "cardId";
+ public static final String groupID = "groupId";
+ }
+
public DBHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -34,7 +49,11 @@ public class DBHelper extends SQLiteOpenHelper
@Override
public void onCreate(SQLiteDatabase db)
{
- // create table for gift cards
+ // create table for card groups
+ db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
+ LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
+
+ // create table for cards
db.execSQL("create table " + LoyaltyCardDbIds.TABLE + "(" +
LoyaltyCardDbIds.ID + " INTEGER primary key autoincrement," +
LoyaltyCardDbIds.STORE + " TEXT not null," +
@@ -44,6 +63,12 @@ public class DBHelper extends SQLiteOpenHelper
LoyaltyCardDbIds.CARD_ID + " TEXT not null," +
LoyaltyCardDbIds.BARCODE_TYPE + " TEXT not null," +
LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0' )");
+
+ // create associative table for cards in groups
+ db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
+ LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
+ LoyaltyCardDbIdsGroups.groupID + " TEXT," +
+ "primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
}
@Override
@@ -64,12 +89,24 @@ public class DBHelper extends SQLiteOpenHelper
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.HEADER_TEXT_COLOR + " INTEGER");
}
+
// Upgrade from version 3 to version 4
if(oldVersion < 4 && newVersion >= 4)
{
db.execSQL("ALTER TABLE " + LoyaltyCardDbIds.TABLE
+ " ADD COLUMN " + LoyaltyCardDbIds.STAR_STATUS + " INTEGER DEFAULT '0'");
+ }
+ // Upgrade from version 4 to version 5
+ if(oldVersion < 5 && newVersion >= 5)
+ {
+ db.execSQL("create table " + LoyaltyCardDbGroups.TABLE + "(" +
+ LoyaltyCardDbGroups.ID + " TEXT primary key not null)");
+
+ db.execSQL("create table " + LoyaltyCardDbIdsGroups.TABLE + "(" +
+ LoyaltyCardDbIdsGroups.cardID + " INTEGER," +
+ LoyaltyCardDbIdsGroups.groupID + " TEXT," +
+ "primary key (" + LoyaltyCardDbIdsGroups.cardID + "," + LoyaltyCardDbIdsGroups.groupID +"))");
}
}
@@ -90,8 +127,8 @@ public class DBHelper extends SQLiteOpenHelper
return newId;
}
- public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id,
- final String store, final String note, final String cardId,
+ public boolean insertLoyaltyCard(final SQLiteDatabase db, final int id, final String store,
+ final String note, final String cardId,
final String barcodeType, final Integer headerColor,
final Integer headerTextColor, final int starStatus)
{
@@ -108,7 +145,6 @@ public class DBHelper extends SQLiteOpenHelper
return (newId != -1);
}
-
public boolean updateLoyaltyCard(final int id, final String store, final String note,
final String cardId, final String barcodeType,
final Integer headerColor, final Integer headerTextColor)
@@ -149,6 +185,7 @@ public class DBHelper extends SQLiteOpenHelper
if(data.getCount() == 1)
{
data.moveToFirst();
+
card = LoyaltyCard.toLoyaltyCard(data);
}
@@ -157,12 +194,76 @@ public class DBHelper extends SQLiteOpenHelper
return card;
}
+ public List getLoyaltyCardGroups(final int id)
+ {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE + " g " +
+ " LEFT JOIN " + LoyaltyCardDbIdsGroups.TABLE + " ig ON ig." + LoyaltyCardDbIdsGroups.groupID + " = g." + LoyaltyCardDbGroups.ID +
+ " where " + LoyaltyCardDbIdsGroups.cardID + "=?" +
+ " ORDER BY " + LoyaltyCardDbIdsGroups.groupID, new String[]{String.format("%d", id)});
+
+ List groups = new ArrayList<>();
+
+ if (!data.moveToFirst()) {
+ return groups;
+ }
+
+ groups.add(Group.toGroup(data));
+
+ while (data.moveToNext()) {
+ groups.add(Group.toGroup(data));
+ }
+
+ return groups;
+ }
+
+ public void setLoyaltyCardGroups(final int id, List groups)
+ {
+ SQLiteDatabase db = getWritableDatabase();
+
+ // First delete lookup table entries associated with this card
+ db.delete(LoyaltyCardDbIdsGroups.TABLE,
+ LoyaltyCardDbIdsGroups.cardID + " = ? ",
+ new String[]{String.format("%d", id)});
+
+ // Then create entries for selected values
+ for (Group group : groups) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
+ contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
+ db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
+ }
+ }
+
+ public void setLoyaltyCardGroups(final SQLiteDatabase db, final int id, List groups)
+ {
+ // First delete lookup table entries associated with this card
+ db.delete(LoyaltyCardDbIdsGroups.TABLE,
+ LoyaltyCardDbIdsGroups.cardID + " = ? ",
+ new String[]{String.format("%d", id)});
+
+ // Then create entries for selected values
+ for (Group group : groups) {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(LoyaltyCardDbIdsGroups.cardID, id);
+ contentValues.put(LoyaltyCardDbIdsGroups.groupID, group._id);
+ db.insert(LoyaltyCardDbIdsGroups.TABLE, null, contentValues);
+ }
+ }
+
public boolean deleteLoyaltyCard (final int id)
{
SQLiteDatabase db = getWritableDatabase();
- int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
+ // Delete card
+ int rowsDeleted = db.delete(LoyaltyCardDbIds.TABLE,
LoyaltyCardDbIds.ID + " = ? ",
new String[]{String.format("%d", id)});
+
+ // And delete lookup table entries associated with this card
+ db.delete(LoyaltyCardDbIdsGroups.TABLE,
+ LoyaltyCardDbIdsGroups.cardID + " = ? ",
+ new String[]{String.format("%d", id)});
+
return (rowsDeleted == 1);
}
@@ -179,16 +280,52 @@ public class DBHelper extends SQLiteOpenHelper
* @return Cursor
*/
public Cursor getLoyaltyCardCursor(final String filter)
+ {
+ return getLoyaltyCardCursor(filter, null);
+ }
+
+ /**
+ * Returns a cursor to all loyalty cards with the filter text in either the store or note in a certain group.
+ *
+ * @param filter
+ * @param group
+ * @return Cursor
+ */
+ public Cursor getLoyaltyCardCursor(final String filter, Group group)
{
String actualFilter = String.format("%%%s%%", filter);
String[] selectionArgs = { actualFilter, actualFilter };
+ StringBuilder groupFilter = new StringBuilder();
+ String limitString = "";
SQLiteDatabase db = getReadableDatabase();
+ if (group != null) {
+ List allowedIds = getGroupCardIds(group._id);
+
+ // Empty group
+ if (allowedIds.size() > 0) {
+ groupFilter.append("AND (");
+
+ for (int i = 0; i < allowedIds.size(); i++) {
+ groupFilter.append(LoyaltyCardDbIds.ID + " = " + allowedIds.get(i));
+ if (i != allowedIds.size() - 1) {
+ groupFilter.append(" OR ");
+ }
+ }
+ groupFilter.append(") ");
+ } else {
+ limitString = "LIMIT 0";
+ }
+ }
+
Cursor res = db.rawQuery("select * from " + LoyaltyCardDbIds.TABLE +
- " WHERE " + LoyaltyCardDbIds.STORE + " LIKE ? " +
- " OR " + LoyaltyCardDbIds.NOTE + " LIKE ? " +
- " ORDER BY " + LoyaltyCardDbIds.STAR_STATUS + " DESC," + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC", selectionArgs, null);
+ " WHERE (" + LoyaltyCardDbIds.STORE + " LIKE ? " +
+ " OR " + LoyaltyCardDbIds.NOTE + " LIKE ? )" +
+ groupFilter.toString() +
+ " ORDER BY " + LoyaltyCardDbIds.STAR_STATUS + " DESC," + LoyaltyCardDbIds.STORE + " COLLATE NOCASE ASC " +
+ limitString, selectionArgs, null);
+
return res;
}
@@ -227,5 +364,172 @@ public class DBHelper extends SQLiteOpenHelper
return numItems;
}
+
+ /**
+ * Returns a cursor to all groups.
+ *
+ * @return Cursor
+ */
+ public Cursor getGroupCursor()
+ {
+ SQLiteDatabase db = getReadableDatabase();
+
+ Cursor res = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
+ " ORDER BY " + LoyaltyCardDbGroups.ID + " COLLATE NOCASE ASC", null, null);
+ return res;
+ }
+
+ public List getGroups() {
+ Cursor data = getGroupCursor();
+
+ List groups = new ArrayList<>();
+
+ if (!data.moveToFirst()) {
+ return groups;
+ }
+
+ groups.add(Group.toGroup(data));
+
+ while (data.moveToNext()) {
+ groups.add(Group.toGroup(data));
+ }
+
+ return groups;
+ }
+
+ public Group getGroup(final String groupName)
+ {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor data = db.rawQuery("select * from " + LoyaltyCardDbGroups.TABLE +
+ " where " + LoyaltyCardDbGroups.ID + "=?", new String[]{groupName});
+
+ Group group = null;
+
+ if(data.getCount() == 1)
+ {
+ data.moveToFirst();
+
+ group = Group.toGroup(data);
+ }
+
+ data.close();
+
+ return group;
+ }
+
+ public int getGroupCount()
+ {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbGroups.TABLE, null);
+
+ int numItems = 0;
+
+ if(data.getCount() == 1)
+ {
+ data.moveToFirst();
+ numItems = data.getInt(0);
+ }
+
+ data.close();
+
+ return numItems;
+ }
+
+ public List getGroupCardIds(final String groupName)
+ {
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor data = db.rawQuery("SELECT " + LoyaltyCardDbIdsGroups.cardID +
+ " FROM " + LoyaltyCardDbIdsGroups.TABLE +
+ " WHERE " + LoyaltyCardDbIdsGroups.groupID + " =? ", new String[]{groupName});
+
+ List cardIds = new ArrayList<>();
+
+ if (!data.moveToFirst()) {
+ return cardIds;
+ }
+
+ cardIds.add(data.getInt(0));
+
+ while (data.moveToNext()) {
+ cardIds.add(data.getInt(0));
+ }
+
+ data.close();
+
+ return cardIds;
+ }
+
+ public long insertGroup(final String name)
+ {
+ if (name.isEmpty()) return -1;
+
+ SQLiteDatabase db = getWritableDatabase();
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(LoyaltyCardDbGroups.ID, name);
+ final long newId = db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
+ return newId;
+ }
+
+ public boolean insertGroup(final SQLiteDatabase db, final String name)
+ {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(LoyaltyCardDbGroups.ID, name);
+ final long newId = db.insert(LoyaltyCardDbGroups.TABLE, null, contentValues);
+ return (newId != -1);
+ }
+
+ public boolean updateGroup(final String groupName, final String newName)
+ {
+ if (newName.isEmpty()) return false;
+
+ SQLiteDatabase db = getWritableDatabase();
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(LoyaltyCardDbGroups.ID, newName);
+ try {
+ int rowsUpdated = db.update(LoyaltyCardDbGroups.TABLE, contentValues,
+ LoyaltyCardDbGroups.ID + "=?",
+ new String[]{groupName});
+ return (rowsUpdated == 1);
+ } catch (android.database.sqlite.SQLiteConstraintException _e) {
+ return false;
+ }
+ }
+
+ public boolean deleteGroup(final String groupName)
+ {
+ SQLiteDatabase db = getWritableDatabase();
+ // Delete group
+ int rowsDeleted = db.delete(LoyaltyCardDbGroups.TABLE,
+ LoyaltyCardDbGroups.ID + " = ? ",
+ new String[]{groupName});
+
+ // And delete lookup table entries associated with this group
+ db.delete(LoyaltyCardDbIdsGroups.TABLE,
+ LoyaltyCardDbIdsGroups.groupID + " = ? ",
+ new String[]{groupName});
+
+ return (rowsDeleted == 1);
+ }
+
+ public int getGroupCardCount(final String groupName)
+ {
+ SQLiteDatabase db = getReadableDatabase();
+
+ Cursor data = db.rawQuery("SELECT Count(*) FROM " + LoyaltyCardDbIdsGroups.TABLE +
+ " where " + LoyaltyCardDbIdsGroups.groupID + "=?",
+ new String[]{groupName});
+
+ int numItems = 0;
+
+ if(data.getCount() == 1)
+ {
+ data.moveToFirst();
+ numItems = data.getInt(0);
+ }
+
+ data.close();
+
+ return numItems;
+ }
}
diff --git a/app/src/main/java/protect/card_locker/Group.java b/app/src/main/java/protect/card_locker/Group.java
new file mode 100644
index 000000000..9846abe73
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/Group.java
@@ -0,0 +1,20 @@
+package protect.card_locker;
+
+import android.database.Cursor;
+
+public class Group
+{
+ public final String _id;
+
+ public Group(final String _id)
+ {
+ this._id = _id;
+ }
+
+ public static Group toGroup(Cursor cursor)
+ {
+ String _id = cursor.getString(cursor.getColumnIndexOrThrow(DBHelper.LoyaltyCardDbGroups.ID));
+
+ return new Group(_id);
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/GroupCursorAdapter.java b/app/src/main/java/protect/card_locker/GroupCursorAdapter.java
new file mode 100644
index 000000000..e06ea7ad6
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/GroupCursorAdapter.java
@@ -0,0 +1,53 @@
+package protect.card_locker;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.TextView;
+
+import protect.card_locker.preferences.Settings;
+
+class GroupCursorAdapter extends CursorAdapter
+{
+ Settings settings;
+ DBHelper db;
+
+ public GroupCursorAdapter(Context context, Cursor cursor)
+ {
+ super(context, cursor, 0);
+ settings = new Settings(context);
+
+ db = new DBHelper(context);
+ }
+
+ // The newView method is used to inflate a new view and return it,
+ // you don't bind any data to the view at this point.
+ @Override
+ public View newView(Context context, Cursor cursor, ViewGroup parent)
+ {
+ return LayoutInflater.from(context).inflate(R.layout.group_layout, parent, false);
+ }
+
+ // The bindView method is used to bind all data to a given view
+ // such as setting the text on a TextView.
+ @Override
+ public void bindView(View view, Context context, Cursor cursor)
+ {
+ // Find fields to populate in inflated template
+ TextView nameField = (TextView) view.findViewById(R.id.name);
+ TextView countField = (TextView) view.findViewById(R.id.cardCount);
+
+ // Extract properties from cursor
+ Group group = Group.toGroup(cursor);
+
+ // Populate fields with extracted properties
+ nameField.setText(group._id);
+ countField.setText(String.format(context.getString(R.string.groupCardCount), db.getGroupCardCount(group._id)));
+
+ nameField.setTextSize(settings.getCardTitleListFontSize());
+ countField.setTextSize(settings.getCardNoteListFontSize());
+ }
+}
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCard.java b/app/src/main/java/protect/card_locker/LoyaltyCard.java
index 6c21b3a38..67cb4de64 100644
--- a/app/src/main/java/protect/card_locker/LoyaltyCard.java
+++ b/app/src/main/java/protect/card_locker/LoyaltyCard.java
@@ -1,6 +1,7 @@
package protect.card_locker;
import android.database.Cursor;
+
import androidx.annotation.Nullable;
public class LoyaltyCard
@@ -20,7 +21,8 @@ public class LoyaltyCard
public final int starStatus;
public LoyaltyCard(final int id, final String store, final String note, final String cardId,
- final String barcodeType, final Integer headerColor, final Integer headerTextColor,final int starStatus)
+ final String barcodeType, final Integer headerColor, final Integer headerTextColor,
+ final int starStatus)
{
this.id = id;
this.store = store;
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java
index 3a36ee7ec..8af102bf8 100644
--- a/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java
+++ b/app/src/main/java/protect/card_locker/LoyaltyCardEditActivity.java
@@ -9,6 +9,8 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import com.google.android.material.chip.Chip;
+import com.google.android.material.chip.ChipGroup;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.ActionBar;
@@ -33,6 +35,8 @@ import com.jaredrummler.android.colorpicker.ColorPickerDialog;
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;
import java.io.InvalidObjectException;
+import java.util.ArrayList;
+import java.util.List;
public class LoyaltyCardEditActivity extends AppCompatActivity
{
@@ -43,6 +47,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
EditText storeFieldEdit;
EditText noteFieldEdit;
+ ChipGroup groupsChips;
ImageView headingColorSample;
Button headingColorSelectButton;
ImageView headingStoreTextColorSample;
@@ -99,6 +104,7 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
storeFieldEdit = findViewById(R.id.storeNameEdit);
noteFieldEdit = findViewById(R.id.noteEdit);
+ groupsChips = findViewById(R.id.groupChips);
headingColorSample = findViewById(R.id.headingColorSample);
headingColorSelectButton = findViewById(R.id.headingColorSelectButton);
headingStoreTextColorSample = findViewById(R.id.headingStoreTextColorSample);
@@ -213,6 +219,40 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
hideBarcode();
}
+ if(groupsChips.getChildCount() == 0)
+ {
+ List existingGroups = db.getGroups();
+
+ List loyaltyCardGroups = db.getLoyaltyCardGroups(loyaltyCardId);
+
+ View groupsView = findViewById(R.id.groupsView);
+ View groupsTableRow = findViewById(R.id.groupsTableRow);
+
+ if (existingGroups.isEmpty()) {
+ groupsView.setVisibility(View.GONE);
+ groupsTableRow.setVisibility(View.GONE);
+ } else {
+ groupsView.setVisibility(View.VISIBLE);
+ groupsTableRow.setVisibility(View.VISIBLE);
+ }
+
+ for (Group group : db.getGroups()) {
+ Chip chip = (Chip) getLayoutInflater().inflate(R.layout.layout_chip_choice, groupsChips, false);
+ chip.setText(group._id);
+ chip.setTag(group);
+
+ chip.setChecked(false);
+ for (Group loyaltyCardGroup : loyaltyCardGroups) {
+ if (loyaltyCardGroup._id.equals(group._id)) {
+ chip.setChecked(true);
+ break;
+ }
+ }
+
+ groupsChips.addView(chip);
+ }
+ }
+
if(headingColorValue == null)
{
// Select a random color to start out with.
@@ -404,6 +444,13 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
return;
}
+ List selectedGroups = new ArrayList<>();
+
+ for (Integer chipId : groupsChips.getCheckedChipIds()) {
+ Chip chip = groupsChips.findViewById(chipId);
+ selectedGroups.add((Group) chip.getTag());
+ }
+
if(updateLoyaltyCard)
{ //update of "starStatus" not necessary, since it cannot be changed in this activity (only in ViewActivity)
db.updateLoyaltyCard(loyaltyCardId, store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue);
@@ -414,6 +461,8 @@ public class LoyaltyCardEditActivity extends AppCompatActivity
loyaltyCardId = (int)db.insertLoyaltyCard(store, note, cardId, barcodeType, headingColorValue, headingStoreTextColorValue, 0);
}
+ db.setLoyaltyCardGroups(loyaltyCardId, selectedGroups);
+
finish();
}
diff --git a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java
index b7c213924..7faada421 100644
--- a/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java
+++ b/app/src/main/java/protect/card_locker/LoyaltyCardViewActivity.java
@@ -1,6 +1,5 @@
package protect.card_locker;
-
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
@@ -34,7 +33,6 @@ import com.google.zxing.BarcodeFormat;
import protect.card_locker.preferences.Settings;
-
public class LoyaltyCardViewActivity extends AppCompatActivity
{
private static final String TAG = "Catima";
diff --git a/app/src/main/java/protect/card_locker/MainActivity.java b/app/src/main/java/protect/card_locker/MainActivity.java
index 8dee2a883..64c45ae38 100644
--- a/app/src/main/java/protect/card_locker/MainActivity.java
+++ b/app/src/main/java/protect/card_locker/MainActivity.java
@@ -11,6 +11,10 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.os.Bundle;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.SearchView;
+import androidx.appcompat.widget.Toolbar;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
@@ -22,13 +26,12 @@ import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.SearchView;
-import androidx.appcompat.widget.Toolbar;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.google.android.material.tabs.TabLayout;
import com.google.common.collect.ImmutableMap;
+
import java.util.Calendar;
+import java.util.List;
import java.util.Map;
import protect.card_locker.preferences.SettingsActivity;
@@ -39,6 +42,7 @@ public class MainActivity extends AppCompatActivity
private Menu menu;
protected String filter = "";
+ protected int selectedTab = 0;
@Override
protected void onCreate(Bundle savedInstanceState)
@@ -48,7 +52,26 @@ public class MainActivity extends AppCompatActivity
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
- updateLoyaltyCardList("");
+ updateLoyaltyCardList(filter, null);
+
+ TabLayout groupsTabLayout = findViewById(R.id.groups);
+ groupsTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
+ @Override
+ public void onTabSelected(TabLayout.Tab tab) {
+ selectedTab = tab.getPosition();
+ updateLoyaltyCardList(filter, tab.getTag());
+ }
+
+ @Override
+ public void onTabUnselected(TabLayout.Tab tab) {
+
+ }
+
+ @Override
+ public void onTabReselected(TabLayout.Tab tab) {
+
+ }
+ });
}
@Override
@@ -64,7 +87,22 @@ public class MainActivity extends AppCompatActivity
}
}
- updateLoyaltyCardList(filter);
+ TabLayout groupsTabLayout = findViewById(R.id.groups);
+ boolean hasReset = updateTabGroups(groupsTabLayout);
+
+ Object group = null;
+
+ if (groupsTabLayout.getTabCount() != 0) {
+ TabLayout.Tab tab = groupsTabLayout.getTabAt(0);
+
+ if (!hasReset) {
+ tab = groupsTabLayout.getTabAt(selectedTab);
+ }
+
+ groupsTabLayout.selectTab(tab);
+ group = tab.getTag();
+ }
+ updateLoyaltyCardList(filter, group);
FloatingActionButton addButton = findViewById(R.id.fabAdd);
addButton.setOnClickListener(new View.OnClickListener() {
@@ -109,17 +147,31 @@ public class MainActivity extends AppCompatActivity
if (!searchView.isIconified()) {
searchView.setIconified(true);
} else {
- super.onBackPressed();
+ TabLayout groupsTabLayout = findViewById(R.id.groups);
+
+ if (groupsTabLayout.getVisibility() == View.VISIBLE && selectedTab != 0) {
+ selectedTab = 0;
+ groupsTabLayout.selectTab(groupsTabLayout.getTabAt(0));
+ } else {
+ super.onBackPressed();
+ }
}
}
- private void updateLoyaltyCardList(String filterText)
+ private void updateLoyaltyCardList(String filterText, Object tag)
{
+ Group group = null;
+ if (tag != null) {
+ group = (Group) tag;
+ }
+
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);
+ Cursor cardCursor = db.getLoyaltyCardCursor(filterText, group);
+
if(db.getLoyaltyCardCount() > 0)
{
// We want the cardList to be visible regardless of the filtered match count
@@ -127,7 +179,7 @@ public class MainActivity extends AppCompatActivity
// the keyboard
cardList.setVisibility(View.VISIBLE);
helpText.setVisibility(View.GONE);
- if(db.getLoyaltyCardCount(filterText) > 0)
+ if(cardCursor.getCount() > 0)
{
noMatchingCardsText.setVisibility(View.GONE);
}
@@ -143,8 +195,6 @@ public class MainActivity extends AppCompatActivity
noMatchingCardsText.setVisibility(View.GONE);
}
- Cursor cardCursor = db.getLoyaltyCardCursor(filterText);
-
final LoyaltyCardCursorAdapter adapter = new LoyaltyCardCursorAdapter(this, cardCursor);
cardList.setAdapter(adapter);
@@ -171,6 +221,53 @@ public class MainActivity extends AppCompatActivity
});
}
+ public boolean updateTabGroups(TabLayout groupsTabLayout)
+ {
+ final DBHelper db = new DBHelper(this);
+
+ List newGroups = db.getGroups();
+
+ if (newGroups.size() == 0) {
+ groupsTabLayout.removeAllTabs();
+ groupsTabLayout.setVisibility(View.GONE);
+ return true;
+ }
+
+ // -1 because there is an "All" tab
+ boolean isChanged = groupsTabLayout.getTabCount() - 1 != newGroups.size();
+
+ if (!isChanged) {
+ for (int i = 0; i < newGroups.size(); i++) {
+ if (!((Group) groupsTabLayout.getTabAt(i + 1).getTag())._id.equals(newGroups.get(i)._id)) {
+ isChanged = true;
+ break;
+ }
+ }
+ }
+
+ if (isChanged) {
+ groupsTabLayout.removeAllTabs();
+
+ TabLayout.Tab allTab = groupsTabLayout.newTab();
+ allTab.setText(R.string.all);
+ allTab.setTag(null);
+ groupsTabLayout.addTab(allTab);
+
+ for (Group group : newGroups) {
+ TabLayout.Tab tab = groupsTabLayout.newTab();
+ tab.setText(group._id);
+ tab.setTag(group);
+ groupsTabLayout.addTab(tab);
+ }
+
+ groupsTabLayout.setVisibility(View.VISIBLE);
+
+ return true;
+ }
+
+ return false;
+ }
+
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
{
@@ -240,7 +337,10 @@ public class MainActivity extends AppCompatActivity
@Override
public boolean onQueryTextChange(String newText) {
filter = newText;
- updateLoyaltyCardList(newText);
+
+ TabLayout groupsTabLayout = findViewById(R.id.groups);
+
+ updateLoyaltyCardList(newText, groupsTabLayout.getTabAt(groupsTabLayout.getSelectedTabPosition()).getTag());
return true;
}
});
@@ -254,6 +354,13 @@ public class MainActivity extends AppCompatActivity
{
int id = item.getItemId();
+ if (id == R.id.action_manage_groups)
+ {
+ Intent i = new Intent(getApplicationContext(), ManageGroupsActivity.class);
+ startActivityForResult(i, MAIN_REQUEST_CODE);
+ return true;
+ }
+
if(id == R.id.action_import_export)
{
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
diff --git a/app/src/main/java/protect/card_locker/ManageGroupsActivity.java b/app/src/main/java/protect/card_locker/ManageGroupsActivity.java
new file mode 100644
index 000000000..1b83723ba
--- /dev/null
+++ b/app/src/main/java/protect/card_locker/ManageGroupsActivity.java
@@ -0,0 +1,186 @@
+package protect.card_locker;
+
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.text.InputType;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+public class ManageGroupsActivity extends AppCompatActivity
+{
+ private static final String TAG = "Catima";
+
+ private AlertDialog newGroupDialog;
+ private final DBHelper db = new DBHelper(this);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.manage_groups_activity);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ ActionBar actionBar = getSupportActionBar();
+ if(actionBar != null)
+ {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ newGroupDialog = createNewGroupDialog();
+
+ updateGroupList();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ updateGroupList();
+
+ FloatingActionButton addButton = findViewById(R.id.fabAdd);
+ addButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ newGroupDialog.show();
+ }
+ });
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ }
+
+ private void updateGroupList()
+ {
+ final ListView groupList = findViewById(R.id.list);
+ final TextView helpText = findViewById(R.id.helpText);
+ final DBHelper db = new DBHelper(this);
+
+ if(db.getGroupCount() > 0)
+ {
+ groupList.setVisibility(View.VISIBLE);
+ helpText.setVisibility(View.GONE);
+ }
+ else
+ {
+ groupList.setVisibility(View.GONE);
+ helpText.setVisibility(View.VISIBLE);
+ }
+
+ Cursor groupCursor = db.getGroupCursor();
+
+ final GroupCursorAdapter adapter = new GroupCursorAdapter(this, groupCursor);
+ groupList.setAdapter(adapter);
+
+ registerForContextMenu(groupList);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item)
+ {
+ int id = item.getItemId();
+
+ if (id == android.R.id.home) {
+ finish();
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ public void editGroup(View view) {
+ LinearLayout parentRow = (LinearLayout) view.getParent();
+
+ TextView groupNameTextView = (TextView) parentRow.findViewById(R.id.name);
+
+ final String groupName = (String) groupNameTextView.getText();
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.enter_group_name);
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+ input.setText(groupName);
+ builder.setView(input);
+
+ builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ db.updateGroup(groupName, input.getText().toString());
+ updateGroupList();
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ public void deleteGroup(View view) {
+ LinearLayout parentRow = (LinearLayout) view.getParent();
+
+ TextView groupNameTextView = (TextView) parentRow.findViewById(R.id.name);
+
+ final String groupName = (String) groupNameTextView.getText();
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.deleteConfirmationGroup);
+ builder.setMessage(groupName);
+
+ builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ db.deleteGroup(groupName);
+ updateGroupList();
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ private AlertDialog createNewGroupDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.enter_group_name);
+ final EditText input = new EditText(this);
+ input.setInputType(InputType.TYPE_CLASS_TEXT);
+ builder.setView(input);
+
+ builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ db.insertGroup(input.getText().toString());
+ updateGroupList();
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog dialog = builder.create();
+
+ return dialog;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_folder_white.png b/app/src/main/res/drawable-hdpi/ic_folder_white.png
new file mode 100644
index 000000000..a667cd078
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_folder_white.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_folder_white.png b/app/src/main/res/drawable-mdpi/ic_folder_white.png
new file mode 100644
index 000000000..56b8a6895
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_folder_white.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_folder_white.png b/app/src/main/res/drawable-xhdpi/ic_folder_white.png
new file mode 100644
index 000000000..12f88a2b3
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_folder_white.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_folder_white.png b/app/src/main/res/drawable-xxhdpi/ic_folder_white.png
new file mode 100644
index 000000000..3eb3a8df9
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_folder_white.png differ
diff --git a/app/src/main/res/layout/group_layout.xml b/app/src/main/res/layout/group_layout.xml
new file mode 100644
index 000000000..787cd38d4
--- /dev/null
+++ b/app/src/main/res/layout/group_layout.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/group_main.xml b/app/src/main/res/layout/group_main.xml
new file mode 100644
index 000000000..11afef1b6
--- /dev/null
+++ b/app/src/main/res/layout/group_main.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_chip_choice.xml b/app/src/main/res/layout/layout_chip_choice.xml
new file mode 100644
index 000000000..fe580c27d
--- /dev/null
+++ b/app/src/main/res/layout/layout_chip_choice.xml
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/loyalty_card_edit_activity.xml b/app/src/main/res/layout/loyalty_card_edit_activity.xml
index 3093f189c..7ace87adb 100644
--- a/app/src/main/res/layout/loyalty_card_edit_activity.xml
+++ b/app/src/main/res/layout/loyalty_card_edit_activity.xml
@@ -145,6 +145,62 @@
android:background="@color/inputBorder" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/manage_groups_activity.xml b/app/src/main/res/layout/manage_groups_activity.xml
new file mode 100644
index 000000000..8c43fba53
--- /dev/null
+++ b/app/src/main/res/layout/manage_groups_activity.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml
index 79514673d..d02adb230 100644
--- a/app/src/main/res/menu/main_menu.xml
+++ b/app/src/main/res/menu/main_menu.xml
@@ -8,6 +8,11 @@
android:icon="@drawable/ic_search_white"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView"/>
+
- >
-
-
-
+
-
+
-
diff --git a/app/src/test/java/protect/card_locker/DatabaseTest.java b/app/src/test/java/protect/card_locker/DatabaseTest.java
index eec0cb64c..30625ed98 100644
--- a/app/src/test/java/protect/card_locker/DatabaseTest.java
+++ b/app/src/test/java/protect/card_locker/DatabaseTest.java
@@ -15,7 +15,11 @@ import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import java.util.ArrayList;
+import java.util.List;
+
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -101,8 +105,6 @@ public class DatabaseTest
assertEquals(BarcodeFormat.UPC_A.toString(), loyaltyCard.barcodeType);
}
-
-
@Test
public void updateMissingGiftCard()
{
@@ -256,6 +258,161 @@ public class DatabaseTest
return (int)newId;
}
+ @Test
+ public void addRemoveOneGroup()
+ {
+ assertEquals(0, db.getGroupCount());
+ long id = db.insertGroup("group one");
+ boolean result = (id != -1);
+ assertTrue(result);
+ assertEquals(1, db.getGroupCount());
+
+ Group group = db.getGroup("group one");
+ assertNotNull(group);
+ assertEquals("group one", group._id);
+
+ result = db.deleteGroup("group one");
+ assertTrue(result);
+ assertEquals(0, db.getGroupCount());
+ assertNull(db.getGroup("group one"));
+ }
+
+ @Test
+ public void updateGroup()
+ {
+ long id = db.insertGroup("group one");
+ boolean result = (id != -1);
+ assertTrue(result);
+ assertEquals(1, db.getGroupCount());
+
+ result = db.updateGroup("group one", "group one renamed");
+ assertTrue(result);
+ assertEquals(1, db.getGroupCount());
+
+ // Group one no longer exists
+ Group group = db.getGroup("group one");
+ assertNull(group);
+
+ // But group one renamed does
+ Group group2 = db.getGroup("group one renamed");
+ assertNotNull(group2);
+ assertEquals("group one renamed", group2._id);
+ }
+
+ @Test
+ public void updateMissingGroup()
+ {
+ assertEquals(0, db.getGroupCount());
+
+ boolean result = db.updateGroup("group one", "new name");
+ assertEquals(false, result);
+ assertEquals(0, db.getGroupCount());
+ }
+
+ @Test
+ public void emptyGroupValues()
+ {
+ long id = db.insertGroup("");
+ boolean result = (id != -1);
+ assertFalse(result);
+ assertEquals(0, db.getLoyaltyCardCount());
+ }
+
+ @Test
+ public void duplicateGroupName()
+ {
+ assertEquals(0, db.getGroupCount());
+ long id = db.insertGroup("group one");
+ boolean result = (id != -1);
+ assertTrue(result);
+ assertEquals(1, db.getGroupCount());
+
+ Group group = db.getGroup("group one");
+ assertNotNull(group);
+ assertEquals("group one", group._id);
+
+ // Should fail on duplicate
+ long id2 = db.insertGroup("group one");
+ boolean result2 = (id2 != -1);
+ assertFalse(result2);
+ assertEquals(1, db.getGroupCount());
+ }
+
+ @Test
+ public void updateGroupDuplicate()
+ {
+ long id = db.insertGroup("group one");
+ boolean result = (id != -1);
+ assertTrue(result);
+ assertEquals(1, db.getGroupCount());
+
+ long id2 = db.insertGroup("group two");
+ boolean result2 = (id2 != -1);
+ assertTrue(result2);
+ assertEquals(2, db.getGroupCount());
+
+ // Should fail when trying to rename group two to one
+ boolean result3 = db.updateGroup("group two", "group one");
+ assertFalse(result3);
+ assertEquals(2, db.getGroupCount());
+
+ // Rename failed so both should still be the same
+ Group group = db.getGroup("group one");
+ assertNotNull(group);
+ assertEquals("group one", group._id);
+
+ Group group2 = db.getGroup("group two");
+ assertNotNull(group2);
+ assertEquals("group two", group2._id);
+ }
+
+ @Test
+ public void cardAddAndRemoveGroups()
+ {
+ // Create card
+ assertEquals(0, db.getLoyaltyCardCount());
+ long id = db.insertLoyaltyCard("store", "note", "cardId", BarcodeFormat.UPC_A.toString(), DEFAULT_HEADER_COLOR, DEFAULT_HEADER_TEXT_COLOR, 0);
+ boolean result = (id != -1);
+ assertTrue(result);
+ assertEquals(1, db.getLoyaltyCardCount());
+
+ // Create two groups to only one card
+ assertEquals(0, db.getGroupCount());
+ long gid = db.insertGroup("one");
+ boolean gresult = (gid != -1);
+ assertTrue(gresult);
+
+ long gid2 = db.insertGroup("two");
+ boolean gresult2 = (gid2 != -1);
+ assertTrue(gresult2);
+
+ assertEquals(2, db.getGroupCount());
+
+ Group group1 = db.getGroup("one");
+
+ // Card has no groups by default
+ List cardGroups = db.getLoyaltyCardGroups(1);
+ assertEquals(0, cardGroups.size());
+
+ // Add one groups to card
+ List groupList1 = new ArrayList<>();
+ groupList1.add(group1);
+ db.setLoyaltyCardGroups(1, groupList1);
+
+ List cardGroups1 = db.getLoyaltyCardGroups(1);
+ assertEquals(1, cardGroups1.size());
+ assertEquals(cardGroups1.get(0)._id, group1._id);
+ assertEquals(1, db.getGroupCardCount("one"));
+ assertEquals(0, db.getGroupCardCount("two"));
+
+ // Remove groups
+ db.setLoyaltyCardGroups(1, new ArrayList());
+ List cardGroups2 = db.getLoyaltyCardGroups(1);
+ assertEquals(0, cardGroups2.size());
+ assertEquals(0, db.getGroupCardCount("one"));
+ assertEquals(0, db.getGroupCardCount("two"));
+ }
+
@Test
public void databaseUpgradeFromVersion1()
{
diff --git a/app/src/test/java/protect/card_locker/ImportExportActivityTest.java b/app/src/test/java/protect/card_locker/ImportExportActivityTest.java
index c6ceb034b..665933c4f 100644
--- a/app/src/test/java/protect/card_locker/ImportExportActivityTest.java
+++ b/app/src/test/java/protect/card_locker/ImportExportActivityTest.java
@@ -9,7 +9,6 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.net.Uri;
import android.view.View;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -17,7 +16,6 @@ import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowPackageManager;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 23)
diff --git a/app/src/test/java/protect/card_locker/ImportExportTest.java b/app/src/test/java/protect/card_locker/ImportExportTest.java
index d3bcc27d2..ff2a0dec0 100644
--- a/app/src/test/java/protect/card_locker/ImportExportTest.java
+++ b/app/src/test/java/protect/card_locker/ImportExportTest.java
@@ -24,7 +24,9 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Calendar;
+import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -100,6 +102,20 @@ public class ImportExportTest
assertEquals(cardsToAdd, db.getLoyaltyCardCount());
}
+ private void addGroups(int groupsToAdd)
+ {
+ // Add in reverse order to test sorting
+ for(int index = groupsToAdd; index > 0; index--)
+ {
+ String groupName = String.format("group, \"%4d", index);
+ long id = db.insertGroup(groupName);
+ boolean result = (id != -1);
+ assertTrue(result);
+ }
+
+ assertEquals(groupsToAdd, db.getGroupCount());
+ }
+
/**
* Check that all of the cards follow the pattern
* specified in addLoyaltyCards(), and are in sequential order
@@ -181,6 +197,29 @@ public class ImportExportTest
cursor.close();
}
+ /**
+ * Check that all of the groups follow the pattern
+ * specified in addGroups(), and are in sequential order
+ * where the smallest group's index is 1
+ */
+ private void checkGroups()
+ {
+ Cursor cursor = db.getGroupCursor();
+ int index = 1;
+
+ while(cursor.moveToNext())
+ {
+ Group group = Group.toGroup(cursor);
+
+ String expectedGroupName = String.format("group, \"%4d", index);
+
+ assertEquals(expectedGroupName, group._id);
+
+ index++;
+ }
+ cursor.close();
+ }
+
/**
* Delete the contents of the database
*/
@@ -263,6 +302,96 @@ public class ImportExportTest
}
}
+ private List groupsToGroupNames(List groups)
+ {
+ List groupNames = new ArrayList<>();
+
+ for (Group group : groups) {
+ groupNames.add(group._id);
+ }
+
+ return groupNames;
+ }
+
+ @Test
+ public void multipleCardsExportImportWithGroups() throws IOException
+ {
+ final int NUM_CARDS = 10;
+ final int NUM_GROUPS = 3;
+
+ for(DataFormat format : DataFormat.values())
+ {
+ addLoyaltyCards(NUM_CARDS);
+ addGroups(NUM_GROUPS);
+
+ List emptyGroup = new ArrayList<>();
+
+ List groupsForOne = new ArrayList<>();
+ groupsForOne.add(db.getGroup("group, \" 1"));
+
+ List groupsForTwo = new ArrayList<>();
+ groupsForTwo.add(db.getGroup("group, \" 1"));
+ groupsForTwo.add(db.getGroup("group, \" 2"));
+
+ List groupsForThree = new ArrayList<>();
+ groupsForThree.add(db.getGroup("group, \" 1"));
+ groupsForThree.add(db.getGroup("group, \" 2"));
+ groupsForThree.add(db.getGroup("group, \" 3"));
+
+ List groupsForFour = new ArrayList<>();
+ groupsForFour.add(db.getGroup("group, \" 1"));
+ groupsForFour.add(db.getGroup("group, \" 2"));
+ groupsForFour.add(db.getGroup("group, \" 3"));
+
+ List groupsForFive = new ArrayList<>();
+ groupsForFive.add(db.getGroup("group, \" 1"));
+ groupsForFive.add(db.getGroup("group, \" 3"));
+
+ db.setLoyaltyCardGroups(1, groupsForOne);
+ db.setLoyaltyCardGroups(2, groupsForTwo);
+ db.setLoyaltyCardGroups(3, groupsForThree);
+ db.setLoyaltyCardGroups(4, groupsForFour);
+ db.setLoyaltyCardGroups(5, groupsForFive);
+
+ ByteArrayOutputStream outData = new ByteArrayOutputStream();
+ OutputStreamWriter outStream = new OutputStreamWriter(outData);
+
+ // Export data to CSV format
+ boolean result = MultiFormatExporter.exportData(db, outStream, format);
+ assertTrue(result);
+ outStream.close();
+
+ clearDatabase();
+
+ ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray());
+ InputStreamReader inStream = new InputStreamReader(inData);
+
+ // Import the CSV data
+ result = MultiFormatImporter.importData(db, inStream, DataFormat.CSV);
+ assertTrue(result);
+
+ assertEquals(NUM_CARDS, db.getLoyaltyCardCount());
+ assertEquals(NUM_GROUPS, db.getGroupCount());
+
+ checkLoyaltyCards();
+ checkGroups();
+
+ assertEquals(groupsToGroupNames(groupsForOne), groupsToGroupNames(db.getLoyaltyCardGroups(1)));
+ assertEquals(groupsToGroupNames(groupsForTwo), groupsToGroupNames(db.getLoyaltyCardGroups(2)));
+ assertEquals(groupsToGroupNames(groupsForThree), groupsToGroupNames(db.getLoyaltyCardGroups(3)));
+ assertEquals(groupsToGroupNames(groupsForFour), groupsToGroupNames(db.getLoyaltyCardGroups(4)));
+ assertEquals(groupsToGroupNames(groupsForFive), groupsToGroupNames(db.getLoyaltyCardGroups(5)));
+ assertEquals(emptyGroup, db.getLoyaltyCardGroups(6));
+ assertEquals(emptyGroup, db.getLoyaltyCardGroups(7));
+ assertEquals(emptyGroup, db.getLoyaltyCardGroups(8));
+ assertEquals(emptyGroup, db.getLoyaltyCardGroups(9));
+ assertEquals(emptyGroup, db.getLoyaltyCardGroups(10));
+
+ // Clear the database for the next format under test
+ clearDatabase();
+ }
+ }
+
@Test
public void importExistingCardsNotReplace() throws IOException
{
@@ -395,7 +524,7 @@ public class ImportExportTest
}
@Test
- public void importWithoutColors() throws IOException
+ public void importWithoutColorsV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
@@ -427,7 +556,7 @@ public class ImportExportTest
}
@Test
- public void importWithoutNullColors() throws IOException
+ public void importWithoutNullColorsV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
@@ -461,7 +590,7 @@ public class ImportExportTest
}
@Test
- public void importWithoutInvalidColors() throws IOException
+ public void importWithoutInvalidColorsV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
@@ -485,7 +614,7 @@ public class ImportExportTest
}
@Test
- public void importWithNoBarcodeType() throws IOException
+ public void importWithNoBarcodeTypeV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
@@ -519,7 +648,7 @@ public class ImportExportTest
}
@Test
- public void importWithStarredField() throws IOException
+ public void importWithStarredFieldV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
@@ -555,7 +684,7 @@ public class ImportExportTest
@Test
- public void importWithNoStarredField() throws IOException
+ public void importWithNoStarredFieldV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
@@ -589,7 +718,7 @@ public class ImportExportTest
}
@Test
- public void importWithInvalidStarField() throws IOException
+ public void importWithInvalidStarFieldV1() throws IOException
{
String csvText = "";
csvText += DBHelper.LoyaltyCardDbIds.ID + "," +
diff --git a/app/src/test/java/protect/card_locker/MainActivityTest.java b/app/src/test/java/protect/card_locker/MainActivityTest.java
index 1a3fb1a21..484a9d0ab 100644
--- a/app/src/test/java/protect/card_locker/MainActivityTest.java
+++ b/app/src/test/java/protect/card_locker/MainActivityTest.java
@@ -2,28 +2,28 @@ package protect.card_locker;
import android.app.Activity;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Color;
-import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.ListView;
import android.widget.TextView;
+import com.google.android.material.tabs.TabLayout;
import com.google.zxing.BarcodeFormat;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.android.controller.ActivityController;
+import java.util.ArrayList;
+import java.util.List;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -60,9 +60,10 @@ public class MainActivityTest
final Menu menu = shadowOf(activity).getOptionsMenu();
assertTrue(menu != null);
- // The settings, search and add button should be present
- assertEquals(menu.size(), 4);
+ // The settings, import/export, groups, search and add button should be present
+ assertEquals(menu.size(), 5);
assertEquals("Search", menu.findItem(R.id.action_search).getTitle().toString());
+ assertEquals("Groups", menu.findItem(R.id.action_manage_groups).getTitle().toString());
assertEquals("Import/Export", menu.findItem(R.id.action_import_export).getTitle().toString());
assertEquals("About", menu.findItem(R.id.action_about).getTitle().toString());
assertEquals("Settings", menu.findItem(R.id.action_settings).getTitle().toString());
@@ -165,6 +166,54 @@ public class MainActivityTest
assertEquals("storeB",cursor.getString(cursor.getColumnIndex("store")));
}
+ @Test
+ public void testGroups()
+ {
+ ActivityController activityController = Robolectric.buildActivity(MainActivity.class).create();
+
+ Activity mainActivity = (Activity)activityController.get();
+ activityController.start();
+ activityController.resume();
+
+ DBHelper db = new DBHelper(mainActivity);
+
+ TabLayout groupTabs = mainActivity.findViewById(R.id.groups);
+
+ // No group tabs by default
+ assertEquals(0, groupTabs.getTabCount());
+
+ // Having at least one group should create two tabs: One all and one for each group
+ db.insertGroup("One");
+ activityController.pause();
+ activityController.resume();
+ assertEquals(2, groupTabs.getTabCount());
+ assertEquals("All", groupTabs.getTabAt(0).getText().toString());
+ assertEquals("One", groupTabs.getTabAt(1).getText().toString());
+
+ // Adding another group should have them sorted alphabetically
+ db.insertGroup("Alphabetical two");
+ activityController.pause();
+ activityController.resume();
+ assertEquals(3, groupTabs.getTabCount());
+ assertEquals("All", groupTabs.getTabAt(0).getText().toString());
+ assertEquals("Alphabetical two", groupTabs.getTabAt(1).getText().toString());
+ assertEquals("One", groupTabs.getTabAt(2).getText().toString());
+
+ // Removing a group should also change the list
+ db.deleteGroup("Alphabetical two");
+ activityController.pause();
+ activityController.resume();
+ assertEquals(2, groupTabs.getTabCount());
+ assertEquals("All", groupTabs.getTabAt(0).getText().toString());
+ assertEquals("One", groupTabs.getTabAt(1).getText().toString());
+
+ // Removing the last group should make the tabs disappear
+ db.deleteGroup("One");
+ activityController.pause();
+ activityController.resume();
+ assertEquals(0, groupTabs.getTabCount());
+ }
+
@Test
public void testFiltering()
{
@@ -177,11 +226,17 @@ public class MainActivityTest
TextView helpText = mainActivity.findViewById(R.id.helpText);
TextView noMatchingCardsText = mainActivity.findViewById(R.id.noMatchingCardsText);
ListView list = mainActivity.findViewById(R.id.list);
+ TabLayout groupTabs = mainActivity.findViewById(R.id.groups);
DBHelper db = new DBHelper(mainActivity);
db.insertLoyaltyCard("The First Store", "Initial note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, 0);
db.insertLoyaltyCard("The Second Store", "Secondary note", "cardId", BarcodeFormat.UPC_A.toString(), Color.BLACK, Color.WHITE, 0);
+ db.insertGroup("Group one");
+ List groups = new ArrayList<>();
+ groups.add(db.getGroup("Group one"));
+ db.setLoyaltyCardGroups(1, groups);
+
activityController.pause();
activityController.resume();
@@ -202,6 +257,27 @@ public class MainActivityTest
assertEquals(2, list.getCount());
+ // Switch to Group one
+ groupTabs.selectTab(groupTabs.getTabAt(1));
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
+ // Switch back to all groups
+ groupTabs.selectTab(groupTabs.getTabAt(0));
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(2, list.getCount());
+
mainActivity.filter = "first";
activityController.pause();
@@ -213,6 +289,27 @@ public class MainActivityTest
assertEquals(1, list.getCount());
+ // Switch to Group one
+ groupTabs.selectTab(groupTabs.getTabAt(1));
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
+ // Switch back to all groups
+ groupTabs.selectTab(groupTabs.getTabAt(0));
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
mainActivity.filter = "initial";
activityController.pause();
@@ -224,6 +321,27 @@ public class MainActivityTest
assertEquals(1, list.getCount());
+ // Switch to Group one
+ groupTabs.selectTab(groupTabs.getTabAt(1));
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
+ // Switch back to all groups
+ groupTabs.selectTab(groupTabs.getTabAt(0));
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
mainActivity.filter = "second";
activityController.pause();
@@ -235,6 +353,27 @@ public class MainActivityTest
assertEquals(1, list.getCount());
+ // Switch to Group one
+ groupTabs.selectTab(groupTabs.getTabAt(1));
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.VISIBLE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(0, list.getCount());
+
+ // Switch back to all groups
+ groupTabs.selectTab(groupTabs.getTabAt(0));
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
mainActivity.filter = "company";
activityController.pause();
@@ -246,6 +385,27 @@ public class MainActivityTest
assertEquals(0, list.getCount());
+ // Switch to Group one
+ groupTabs.selectTab(groupTabs.getTabAt(1));
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.VISIBLE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(0, list.getCount());
+
+ // Switch back to all groups
+ groupTabs.selectTab(groupTabs.getTabAt(0));
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.VISIBLE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(0, list.getCount());
+
mainActivity.filter = "";
activityController.pause();
@@ -256,5 +416,26 @@ public class MainActivityTest
assertEquals(View.VISIBLE, list.getVisibility());
assertEquals(2, list.getCount());
+
+ // Switch to Group one
+ groupTabs.selectTab(groupTabs.getTabAt(1));
+
+ activityController.pause();
+ activityController.resume();
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(1, list.getCount());
+
+ // Switch back to all groups
+ groupTabs.selectTab(groupTabs.getTabAt(0));
+
+ assertEquals(View.GONE, helpText.getVisibility());
+ assertEquals(View.GONE, noMatchingCardsText.getVisibility());
+ assertEquals(View.VISIBLE, list.getVisibility());
+
+ assertEquals(2, list.getCount());
}
}
\ No newline at end of file