Merge branch 'master' into aaron-branch-2

This commit is contained in:
Clayton Wilson 2020-10-31 20:58:25 -04:00 committed by GitHub
commit a13f2d288f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 578 additions and 137 deletions

View File

@ -7,19 +7,18 @@ import java.util.Map;
public class ItemSearcher implements CallHandler { public class ItemSearcher implements CallHandler {
DBConnector connector; Connection connection;
String cognitoID; String cognitoID;
private final String GET_ITEM_MATCHES = "SELECT * FROM Product WHERE description LIKE ?"; private final String GET_ITEM_MATCHES = "SELECT * FROM Product WHERE description LIKE ? LIMIT 100;";
public ItemSearcher(DBConnector connector, String cognitoID) { public ItemSearcher(Connection connection, String cognitoID) {
this.connector = connector; this.connection = connection;
this.cognitoID = cognitoID; this.cognitoID = cognitoID;
} }
@Override @Override
public Object conductAction(Map<String, Object> body, HashMap<String, String> queryParams, String s) throws SQLException { public Object conductAction(Map<String, Object> body, HashMap<String, String> queryParams, String s) throws SQLException {
try (Connection connection = connector.getConnection()) {
PreparedStatement getItemMatches = connection.prepareStatement(GET_ITEM_MATCHES); PreparedStatement getItemMatches = connection.prepareStatement(GET_ITEM_MATCHES);
getItemMatches.setString(1, "%" + queryParams.get("id") + "%"); getItemMatches.setString(1, "%" + queryParams.get("id") + "%");
System.out.println(getItemMatches); System.out.println(getItemMatches);
@ -29,4 +28,3 @@ public class ItemSearcher implements CallHandler {
return searchResultsObject; return searchResultsObject;
} }
} }
}

View File

@ -51,5 +51,5 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.11.0' implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation 'com.squareup.okhttp3:okhttp:4.8.1' implementation 'com.squareup.okhttp3:okhttp:4.8.1'
implementation 'com.crystal:crystalrangeseekbar:1.1.3'
} }

View File

@ -1,5 +1,8 @@
package com.example.listify; package com.example.listify;
import android.app.Dialog;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle; import android.os.Bundle;
import com.amplifyframework.auth.AuthException; import com.amplifyframework.auth.AuthException;
@ -11,6 +14,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import android.view.View; import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@ -71,6 +76,8 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr
@Override @Override
public void onClick(View v) { public void onClick(View v) {
closeFABMenu(); closeFABMenu();
LoadingCircleDialog loadingDialog = new LoadingCircleDialog(ItemDetails.this);
loadingDialog.show();
Properties configs = new Properties(); Properties configs = new Properties();
try { try {
@ -81,23 +88,59 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr
Requestor requestor = new Requestor(am, configs.getProperty("apiKey")); Requestor requestor = new Requestor(am, configs.getProperty("apiKey"));
SynchronousReceiver<Integer[]> listIdsReceiver = new SynchronousReceiver<>(); SynchronousReceiver<Integer[]> listIdsReceiver = new SynchronousReceiver<>();
SynchronousReceiver<List> listReceiver = new SynchronousReceiver<>();
requestor.getListOfIds(List.class, listIdsReceiver, listIdsReceiver); requestor.getListOfIds(List.class, listIdsReceiver, listIdsReceiver);
Thread t = new Thread(new Runnable() {
@Override
public void run() {
Integer[] listIds = null;
try { try {
Integer[] listIds = listIdsReceiver.await(); listIds = listIdsReceiver.await();
for (int i = 0; i < listIds.length; i++) {
requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver);
shoppingLists.add(listReceiver.await());
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
// Create threads and add them to a list
Thread[] threads = new Thread[listIds.length];
List[] results = new List[listIds.length];
for (int i = 0; i < listIds.length; i++) {
SynchronousReceiver<List> listReceiver = new SynchronousReceiver<>();
requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver);
final int finalI = i;
Thread l = new Thread(new Runnable() {
@Override
public void run() {
try {
results[finalI] = listReceiver.await();
} catch (Exception e) {
e.printStackTrace();
}
}
});
threads[i] = l;
l.start();
}
shoppingLists.clear();
// Wait for each thread to finish and add results to shoppingLists
for (int i = 0; i < threads.length; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
shoppingLists.add(results[i]);
}
loadingDialog.cancel();
ListPickerDialogFragment listPickerDialog = new ListPickerDialogFragment(shoppingLists); ListPickerDialogFragment listPickerDialog = new ListPickerDialogFragment(shoppingLists);
listPickerDialog.show(getSupportFragmentManager(), "User Lists"); listPickerDialog.show(getSupportFragmentManager(), "User Lists");
} }
}); });
t.start();
}
});
linCreateList.setOnClickListener(new View.OnClickListener() { linCreateList.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -157,7 +200,7 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr
} }
// Add the viewed item to the selected list // Add the selected item to the selected list
@Override @Override
public void sendListSelection(int selectedListIndex, int quantity) { public void sendListSelection(int selectedListIndex, int quantity) {
@ -184,6 +227,8 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr
// Create a new list and add the item to it // Create a new list and add the item to it
@Override @Override
public void sendNewListName(String name, int quantity) { public void sendNewListName(String name, int quantity) {
LoadingCircleDialog loadingDialog = new LoadingCircleDialog(this);
loadingDialog.show();
Properties configs = new Properties(); Properties configs = new Properties();
try { try {
@ -196,16 +241,34 @@ public class ItemDetails extends AppCompatActivity implements ListPickerDialogFr
com.example.listify.data.List newList = new List(-1, name, "user filled by lambda", Instant.now().toEpochMilli()); com.example.listify.data.List newList = new List(-1, name, "user filled by lambda", Instant.now().toEpochMilli());
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try { try {
requestor.postObject(newList, idReceiver, idReceiver); requestor.postObject(newList, idReceiver, idReceiver);
int newListId = idReceiver.await(); int newListId = idReceiver.await();
ListEntry entry = new ListEntry(newListId, curProduct.getItemId(), quantity, Instant.now().toEpochMilli(),false); ListEntry entry = new ListEntry(newListId, curProduct.getItemId(), quantity, Instant.now().toEpochMilli(),false);
requestor.postObject(entry); requestor.postObject(entry);
Toast.makeText(this, String.format("%s created and item added", name), Toast.LENGTH_LONG).show(); runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ItemDetails.this, String.format("%s created and item added", name), Toast.LENGTH_LONG).show();
loadingDialog.cancel();
}
});
} catch (Exception e) { } catch (Exception e) {
Toast.makeText(this, "An error occurred", Toast.LENGTH_LONG).show(); runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(ItemDetails.this, "An error occurred", Toast.LENGTH_LONG).show();
loadingDialog.cancel();
e.printStackTrace(); e.printStackTrace();
} }
});
}
}
});
t.start();
} }
} }

View File

@ -28,7 +28,7 @@ import org.json.JSONException;
import static com.example.listify.MainActivity.am; import static com.example.listify.MainActivity.am;
public class ListPage extends AppCompatActivity { public class ListPage extends AppCompatActivity implements Requestor.Receiver {
ListView listView; ListView listView;
MyAdapter myAdapter; MyAdapter myAdapter;
Requestor requestor; Requestor requestor;
@ -37,6 +37,8 @@ public class ListPage extends AppCompatActivity {
Button decrQuan; Button decrQuan;
Button removeItem; Button removeItem;
Button clearAll; Button clearAll;
TextView tvTotalPrice;
ProgressBar loadingListItems;
ArrayList<String> pNames = new ArrayList<>(); ArrayList<String> pNames = new ArrayList<>();
ArrayList<String> pStores = new ArrayList<>(); ArrayList<String> pStores = new ArrayList<>();
@ -46,11 +48,14 @@ public class ListPage extends AppCompatActivity {
ArrayList<ListEntry> pListItemPair = new ArrayList<>(); ArrayList<ListEntry> pListItemPair = new ArrayList<>();
double totalPrice = 0;
Map<String, Double> totalPriceByStore = new HashMap<>(); Map<String, Double> totalPriceByStore = new HashMap<>();
Map<String, Integer> storeHeaderIndex = new HashMap<>(); Map<String, Integer> storeHeaderIndex = new HashMap<>();
DecimalFormat df = new DecimalFormat("0.00"); DecimalFormat df = new DecimalFormat("0.00");
// TODO: Display a message if their list is empty
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -58,6 +63,17 @@ public class ListPage extends AppCompatActivity {
final int listID = (int) getIntent().getSerializableExtra("listID"); final int listID = (int) getIntent().getSerializableExtra("listID");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
listView = findViewById(R.id.listView);
myAdapter = new MyAdapter(this, pNames, pStores, pPrices, pQuantity, pImages);
listView.setAdapter(myAdapter);
loadingListItems = findViewById(R.id.progress_loading_list_items);
loadingListItems.setVisibility(View.VISIBLE);
clearAll = (Button) findViewById(R.id.buttonClear); clearAll = (Button) findViewById(R.id.buttonClear);
clearAll.setOnClickListener(new View.OnClickListener() { clearAll.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -79,6 +95,7 @@ public class ListPage extends AppCompatActivity {
} }
}); });
Properties configs = new Properties(); Properties configs = new Properties();
try { try {
configs = AuthManager.loadProperties(this, "android.resource://" + getPackageName() + "/raw/auths.json"); configs = AuthManager.loadProperties(this, "android.resource://" + getPackageName() + "/raw/auths.json");
@ -86,17 +103,13 @@ public class ListPage extends AppCompatActivity {
e.printStackTrace(); e.printStackTrace();
} }
requestor = new Requestor(am, configs.getProperty("apiKey")); requestor = new Requestor(am, configs.getProperty("apiKey"));
SynchronousReceiver<List> lr = new SynchronousReceiver<>();
requestor.getObject(Integer.toString(listID), List.class, lr);
List list; requestor.getObject(Integer.toString(listID), List.class, this);
}
try { @Override
list = lr.await(); public void acceptDelivery(Object delivered) {
} List list = (List) delivered;
catch (Exception e) {
list = null;
}
if(list != null) { if(list != null) {
for (ListEntry entry : list.getEntries()) { for (ListEntry entry : list.getEntries()) {
@ -150,13 +163,23 @@ public class ListPage extends AppCompatActivity {
} }
} }
} }
}
// Increment total price
totalPrice += (item.getPrice().doubleValue() * entry.getQuantity());
} }
} }
listView = findViewById(R.id.listView);
myAdapter = new MyAdapter(this, pNames, pStores, pPrices, pQuantity, pImages); tvTotalPrice = (TextView) findViewById(R.id.total_price);
listView.setAdapter(myAdapter); runOnUiThread(new Runnable() {
@Override
public void run() {
tvTotalPrice.setText(String.format("$%.2f", totalPrice));
loadingListItems.setVisibility(View.GONE);
myAdapter.notifyDataSetChanged();
}
});
}
} }
class MyAdapter extends ArrayAdapter<String> { class MyAdapter extends ArrayAdapter<String> {
@ -197,6 +220,10 @@ public class ListPage extends AppCompatActivity {
ListEntry le = pListItemPair.remove(position); ListEntry le = pListItemPair.remove(position);
le.setQuantity(le.getQuantity() - 1); le.setQuantity(le.getQuantity() - 1);
pListItemPair.add(position, le); pListItemPair.add(position, le);
totalPrice -= Double.parseDouble(pPrices.get(position));
tvTotalPrice.setText(String.format("$%.2f", totalPrice));
SynchronousReceiver<Integer> synchronousenforcer = new SynchronousReceiver<>(); SynchronousReceiver<Integer> synchronousenforcer = new SynchronousReceiver<>();
requestor.deleteObject(le, synchronousenforcer, synchronousenforcer); requestor.deleteObject(le, synchronousenforcer, synchronousenforcer);
try { try {
@ -228,6 +255,10 @@ public class ListPage extends AppCompatActivity {
ListEntry le = pListItemPair.remove(position); ListEntry le = pListItemPair.remove(position);
le.setQuantity(le.getQuantity() + 1); le.setQuantity(le.getQuantity() + 1);
pListItemPair.add(position, le); pListItemPair.add(position, le);
totalPrice += Double.parseDouble(pPrices.get(position));
tvTotalPrice.setText(String.format("$%.2f", totalPrice));
SynchronousReceiver<Integer> synchronousenforcer = new SynchronousReceiver<>(); SynchronousReceiver<Integer> synchronousenforcer = new SynchronousReceiver<>();
requestor.deleteObject(le, synchronousenforcer, synchronousenforcer); requestor.deleteObject(le, synchronousenforcer, synchronousenforcer);
try { try {
@ -255,6 +286,10 @@ public class ListPage extends AppCompatActivity {
totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position)))); totalPriceByStore.put("Kroger", totalPriceByStore.get("Kroger") - (Double.parseDouble(pPrices.get(position)) * Integer.parseInt(pQuantity.get(position))));
pPrices.set(storeHeaderIndex.get(pStores.get(position)), df.format(totalPriceByStore.get(pStores.get(position)))); pPrices.set(storeHeaderIndex.get(pStores.get(position)), df.format(totalPriceByStore.get(pStores.get(position))));
totalPrice -= (Double.parseDouble(pPrices.get(position)) *
Double.parseDouble(pQuantity.get(position)));
tvTotalPrice.setText(String.format("$%.2f", totalPrice));
pNames.remove(position); pNames.remove(position);
pStores.remove(position); pStores.remove(position);
pPrices.remove(position); pPrices.remove(position);

View File

@ -0,0 +1,36 @@
package com.example.listify;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.Window;
import android.view.WindowManager;
public class LoadingCircleDialog {
Dialog loadingDialog;
public LoadingCircleDialog(Context context) {
loadingDialog = new Dialog(context);
// Create and show a loading dialog
loadingDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
loadingDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// layout to display
loadingDialog.setContentView(R.layout.dialog_loading);
// set color transpartent
loadingDialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
loadingDialog.setCancelable(false);
loadingDialog.setCanceledOnTouchOutside(false);
}
public void show() {
loadingDialog.show();
}
public void cancel() {
loadingDialog.cancel();
}
}

View File

@ -9,6 +9,7 @@ import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.SearchView; import android.widget.SearchView;
import com.example.listify.adapter.SearchResultsListAdapter; import com.example.listify.adapter.SearchResultsListAdapter;
import com.example.listify.data.ItemSearch; import com.example.listify.data.ItemSearch;
@ -22,8 +23,8 @@ import java.util.Properties;
import static com.example.listify.MainActivity.am; import static com.example.listify.MainActivity.am;
public class SearchResults extends AppCompatActivity implements SortDialogFragment.OnSortingListener { public class SearchResults extends AppCompatActivity implements SortDialogFragment.OnSortingListener, Requestor.Receiver {
private ListView listView; private ProgressBar loadingSearch;
private SearchResultsListAdapter searchResultsListAdapter; private SearchResultsListAdapter searchResultsListAdapter;
private List<Product> resultsProductList = new ArrayList<>(); private List<Product> resultsProductList = new ArrayList<>();
private List<Product> resultsProductListSorted = new ArrayList<>(); private List<Product> resultsProductListSorted = new ArrayList<>();
@ -31,12 +32,16 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
private int storeSelection; private int storeSelection;
private int sortMode; private int sortMode;
private boolean descending; private boolean descending;
private double minPrice = 0;
private double maxPrice = -1;
@Override @Override
public void sendSort(int storeSelection, int sortMode, boolean descending) { public void sendSort(int storeSelection, int sortMode, boolean descending, double minPrice, double maxPrice) {
this.storeSelection = storeSelection; this.storeSelection = storeSelection;
this.sortMode = sortMode; this.sortMode = sortMode;
this.descending = descending; this.descending = descending;
this.minPrice = minPrice;
this.maxPrice = maxPrice;
sortResults(); sortResults();
} }
@ -47,6 +52,8 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
loadingSearch = (ProgressBar) findViewById(R.id.progress_loading_search);
// Back button closes this activity and returns to previous activity (MainActivity) // Back button closes this activity and returns to previous activity (MainActivity)
ImageButton backButton = (ImageButton) findViewById(R.id.backToHomeButton); ImageButton backButton = (ImageButton) findViewById(R.id.backToHomeButton);
backButton.setOnClickListener(new View.OnClickListener() { backButton.setOnClickListener(new View.OnClickListener() {
@ -77,13 +84,12 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
} }
}); });
listView = (ListView) findViewById(R.id.search_results_list); ListView listView = (ListView) findViewById(R.id.search_results_list);
searchResultsListAdapter = new SearchResultsListAdapter(this, resultsProductListSorted); searchResultsListAdapter = new SearchResultsListAdapter(this, resultsProductListSorted);
listView.setAdapter(searchResultsListAdapter); listView.setAdapter(searchResultsListAdapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Toast.makeText(SearchResults.this, resultsProductListSorted.get(position).getItemName(), Toast.LENGTH_SHORT).show();
Intent itemDetailsPage = new Intent(SearchResults.this, ItemDetails.class); Intent itemDetailsPage = new Intent(SearchResults.this, ItemDetails.class);
// Send the selected product // Send the selected product
@ -96,6 +102,15 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override @Override
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(String query) {
// Show progress bar
loadingSearch.setVisibility(View.VISIBLE);
// Clear the old search results
resultsProductList.clear();
// Clear old search results from the view
resultsProductListSorted.clear();
searchResultsListAdapter.notifyDataSetChanged();
doSearch(query); doSearch(query);
return false; return false;
} }
@ -106,6 +121,8 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
} }
}); });
// TODO: Change this to a menu in which sort and filter are two different options
// TODO: Sort should be disabled until a search is made
// Create a dialog for filtering and sorting search results // Create a dialog for filtering and sorting search results
ImageButton sortButton = (ImageButton) findViewById(R.id.results_sort_button); ImageButton sortButton = (ImageButton) findViewById(R.id.results_sort_button);
sortButton.setOnClickListener(new View.OnClickListener() { sortButton.setOnClickListener(new View.OnClickListener() {
@ -118,16 +135,36 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
return o1.compareTo(o2); return o1.compareTo(o2);
} }
}); });
SortDialogFragment sortDialog = new SortDialogFragment(storeSelection, stores, sortMode, descending);
// Determine the max price for the price slider
double maxProductPrice;
if (resultsProductList.isEmpty()) {
// default to $100
maxProductPrice = 100.00;
minPrice = 0;
maxPrice = 100;
} else {
maxProductPrice = resultsProductList.get(0).getPrice().doubleValue();
for (int i = 1; i < resultsProductList.size(); i++) {
if (resultsProductList.get(i).getPrice().doubleValue() > maxProductPrice) {
maxProductPrice = resultsProductList.get(i).getPrice().doubleValue();
}
}
if (maxPrice == -1) {
maxPrice = maxProductPrice;
}
}
// Round up to nearest whole number for display on price seekbar
maxProductPrice = Math.ceil(maxProductPrice);
SortDialogFragment sortDialog = new SortDialogFragment(storeSelection, stores, sortMode, descending, maxProductPrice, minPrice, maxPrice);
sortDialog.show(getSupportFragmentManager(), "Sort"); sortDialog.show(getSupportFragmentManager(), "Sort");
} }
}); });
} }
// Override default phone back button to add animation // Override default phone back button to add animation
@Override @Override
public void onBackPressed() { public void onBackPressed() {
@ -136,10 +173,6 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
} }
private void doSearch(String query) { private void doSearch(String query) {
// Clear the old search results
resultsProductList.clear();
Properties configs = new Properties(); Properties configs = new Properties();
try { try {
configs = AuthManager.loadProperties(this, "android.resource://" + getPackageName() + "/raw/auths.json"); configs = AuthManager.loadProperties(this, "android.resource://" + getPackageName() + "/raw/auths.json");
@ -148,34 +181,10 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
} }
Requestor requestor = new Requestor(am, configs.getProperty("apiKey")); Requestor requestor = new Requestor(am, configs.getProperty("apiKey"));
requestor.getObject(query, ItemSearch.class, this);
SynchronousReceiver<ItemSearch> itemReceiver = new SynchronousReceiver<>();
requestor.getObject(query, ItemSearch.class, itemReceiver, itemReceiver);
ItemSearch results;
try {
results = itemReceiver.await();
for (int i = 0; i < results.getResults().size(); i++) {
// TODO: Change to dynamically grab chain name by id
resultsProductList.add(new Product(results.getResults().get(i).getDescription(), results.getResults().get(i).getProductID(), "Kroger", results.getResults().get(i).getChainID(), results.getResults().get(i).getUpc(), results.getResults().get(i).getDescription(), results.getResults().get(i).getPrice(), results.getResults().get(i).getImageURL(), results.getResults().get(i).getDepartment()));
}
} catch (Exception e) {
e.printStackTrace();
}
// Create a list of all stores in the results so the user can filter by store name
for (int i = 0; i < resultsProductList.size(); i++) {
if (!stores.contains(resultsProductList.get(i).getChainName())) {
stores.add(resultsProductList.get(i).getChainName());
}
}
// Add all results to the sorted list
resultsProductListSorted.addAll(resultsProductList);
// Apply selected sorting to the list
sortResults();
} }
// TODO: Scroll the list back to the top when a search, sort, or filter is performed
// Sorts the search results // Sorts the search results
private void sortResults() { private void sortResults() {
// Reset the filtered list // Reset the filtered list
@ -203,7 +212,6 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
}); });
break; break;
// TODO: May need to change this depending on if price is stored as a string or a double
case 2: case 2:
resultsProductListSorted.sort(new Comparator<Product>() { resultsProductListSorted.sort(new Comparator<Product>() {
@Override @Override
@ -238,6 +246,7 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
break; break;
} }
// Flip the list if descending is selected
if (this.sortMode != 0 & this.descending) { if (this.sortMode != 0 & this.descending) {
for (int i = 0; i < resultsProductListSorted.size() / 2; i++) { for (int i = 0; i < resultsProductListSorted.size() / 2; i++) {
Product temp = resultsProductListSorted.get(i); Product temp = resultsProductListSorted.get(i);
@ -258,6 +267,71 @@ public class SearchResults extends AppCompatActivity implements SortDialogFragme
resultsProductListSorted.addAll(temp); resultsProductListSorted.addAll(temp);
} }
// Filter out products that don't fit price restraints
ArrayList<Product> temp = new ArrayList<>();
resultsProductListSorted.forEach(product -> {
if (product.getPrice().doubleValue() >= this.minPrice &&
(this.maxPrice == -1 || product.getPrice().doubleValue() <= this.maxPrice)) {
temp.add(product);
}
});
resultsProductListSorted.clear();
resultsProductListSorted.addAll(temp);
runOnUiThread(new Runnable() {
@Override
public void run() {
searchResultsListAdapter.notifyDataSetChanged(); searchResultsListAdapter.notifyDataSetChanged();
} }
});
}
// This is called after the search results come back from the server
// TODO: Display a "no results" message if nothing is found when searching
@Override
public void acceptDelivery(Object delivered) {
ItemSearch results = (ItemSearch) delivered;
try {
for (int i = 0; i < results.getResults().size(); i++) {
// TODO: Change to dynamically grab chain name by id
resultsProductList.add(new Product(
results.getResults().get(i).getDescription(),
results.getResults().get(i).getProductID(),
"Kroger",
results.getResults().get(i).getChainID(),
results.getResults().get(i).getUpc(),
results.getResults().get(i).getDescription(),
results.getResults().get(i).getPrice(),
results.getResults().get(i).getImageURL(),
results.getResults().get(i).getDepartment()
));
}
} catch (Exception e) {
e.printStackTrace();
}
// Create a list of all stores in the results so the user can filter by store name
for (int i = 0; i < resultsProductList.size(); i++) {
if (!stores.contains(resultsProductList.get(i).getChainName())) {
stores.add(resultsProductList.get(i).getChainName());
}
}
// Add all results to the sorted list
resultsProductListSorted.addAll(resultsProductList);
// Apply selected sorting to the list
sortResults();
// Updates the list of search results. Runs on the main UI thread since other threads are
// not allowed to change UI elements
runOnUiThread(new Runnable() {
@Override
public void run() {
// Hide progress bar
loadingSearch.setVisibility(View.GONE);
}
});
}
} }

View File

@ -10,30 +10,44 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import com.crystal.crystalrangeseekbar.interfaces.OnRangeSeekbarChangeListener;
import com.crystal.crystalrangeseekbar.widgets.CrystalRangeSeekbar;
import java.util.ArrayList; import java.util.ArrayList;
public class SortDialogFragment extends DialogFragment { public class SortDialogFragment extends DialogFragment {
public interface OnSortingListener { public interface OnSortingListener {
void sendSort(int storeSelection, int sortMode, boolean descending); void sendSort(int storeSelection, int sortMode, boolean descending, double minPrice, double maxPrice);
} }
public OnSortingListener onSortingListener; public OnSortingListener onSortingListener;
CrystalRangeSeekbar priceSeekbar;
private int storeSelection; private int storeSelection;
private int sortMode; private int sortMode;
private boolean descending; private boolean descending;
private ArrayList<String> stores; private ArrayList<String> stores;
private double maxProductPrice; // The highest price on the slider
private double minPrice; // The selected min price
private double maxPrice; // The selected max price
public SortDialogFragment(int storeSelection, ArrayList<String> stores, int sortMode, boolean descending) { public SortDialogFragment(int storeSelection, ArrayList<String> stores, int sortMode, boolean descending, double maxProductPrice, double minPrice, double maxPrice) {
this.storeSelection = storeSelection; this.storeSelection = storeSelection;
this.stores = stores; this.stores = stores;
this.sortMode = sortMode; this.sortMode = sortMode;
this.descending = descending; this.descending = descending;
this.maxProductPrice = maxProductPrice;
this.minPrice = minPrice;
this.maxPrice = maxPrice;
} }
@ -51,7 +65,7 @@ public class SortDialogFragment extends DialogFragment {
.setPositiveButton("OK", new DialogInterface.OnClickListener() { .setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
onSortingListener.sendSort(storeSelection, sortMode, descending); onSortingListener.sendSort(storeSelection, sortMode, descending, priceSeekbar.getSelectedMinValue().doubleValue(), priceSeekbar.getSelectedMaxValue().doubleValue());
} }
}) })
.setNegativeButton("cancel", new DialogInterface.OnClickListener() { .setNegativeButton("cancel", new DialogInterface.OnClickListener() {
@ -91,7 +105,7 @@ public class SortDialogFragment extends DialogFragment {
sortDirectionButton.setImageResource(R.drawable.ic_baseline_arrow_upward_50); sortDirectionButton.setImageResource(R.drawable.ic_baseline_arrow_upward_50);
} }
// Change array pointing direction whenever the user clicks the button // Change arrow pointing direction whenever the user clicks the button
sortDirectionButton.setOnClickListener(new View.OnClickListener() { sortDirectionButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -125,9 +139,7 @@ public class SortDialogFragment extends DialogFragment {
} }
@Override @Override
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(AdapterView<?> parent) {}
}
}); });
// Disable the direction button if they have the default sorting mode selected // Disable the direction button if they have the default sorting mode selected
@ -136,6 +148,25 @@ public class SortDialogFragment extends DialogFragment {
sortDirectionButton.setEnabled(false); sortDirectionButton.setEnabled(false);
} }
// Set up the seekbar for price
priceSeekbar = (CrystalRangeSeekbar) root.findViewById(R.id.price_range_seekbar);
final TextView tvMin = (TextView) root.findViewById(R.id.tv_min_price);
final TextView tvMax = (TextView) root.findViewById(R.id.tv_max_price);
priceSeekbar.setMaxValue((float) this.maxProductPrice);
priceSeekbar.setMinStartValue((float) this.minPrice);
priceSeekbar.setMaxStartValue((float) this.maxPrice);
priceSeekbar.apply();
// Update price display
priceSeekbar.setOnRangeSeekbarChangeListener(new OnRangeSeekbarChangeListener() {
@Override
public void valueChanged(Number minValue, Number maxValue) {
tvMin.setText(String.format("$%.2f", minValue.doubleValue()));
tvMax.setText(String.format("$%.2f", maxValue.doubleValue()));
}
});
return builder.create(); return builder.create();
} }

View File

@ -1,15 +1,22 @@
package com.example.listify.ui.lists; package com.example.listify.ui.lists;
import android.app.Dialog;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.ListFragment;
import com.amplifyframework.auth.AuthException; import com.amplifyframework.auth.AuthException;
import com.example.listify.AuthManager; import com.example.listify.AuthManager;
@ -17,6 +24,7 @@ import com.example.listify.CreateListAddDialogFragment;
import com.example.listify.CreateListDialogFragment; import com.example.listify.CreateListDialogFragment;
import com.example.listify.ItemDetails; import com.example.listify.ItemDetails;
import com.example.listify.ListPage; import com.example.listify.ListPage;
import com.example.listify.LoadingCircleDialog;
import com.example.listify.R; import com.example.listify.R;
import com.example.listify.Requestor; import com.example.listify.Requestor;
import com.example.listify.SearchResults; import com.example.listify.SearchResults;
@ -34,16 +42,20 @@ import java.util.Properties;
import static com.example.listify.MainActivity.am; import static com.example.listify.MainActivity.am;
public class ListsFragment extends Fragment implements CreateListDialogFragment.OnNewListListener { public class ListsFragment extends Fragment implements CreateListDialogFragment.OnNewListListener, Requestor.Receiver {
ArrayList<List> shoppingLists = new ArrayList<>(); ArrayList<List> shoppingLists = new ArrayList<>();
DisplayShoppingListsAdapter displayShoppingListsAdapter; DisplayShoppingListsAdapter displayShoppingListsAdapter;
Requestor requestor;
ListView shoppingListsView; ListView shoppingListsView;
ProgressBar loadingLists;
int resultsIndex;
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_lists, container, false); View root = inflater.inflate(R.layout.fragment_lists, container, false);
shoppingListsView = root.findViewById(R.id.shopping_lists); shoppingListsView = root.findViewById(R.id.shopping_lists);
loadingLists = (ProgressBar) root.findViewById(R.id.progress_loading_lists);
loadingLists.setVisibility(View.VISIBLE);
// TODO: Switch this to async
Properties configs = new Properties(); Properties configs = new Properties();
try { try {
configs = AuthManager.loadProperties(getContext(), "android.resource://" + getActivity().getPackageName() + "/raw/auths.json"); configs = AuthManager.loadProperties(getContext(), "android.resource://" + getActivity().getPackageName() + "/raw/auths.json");
@ -51,36 +63,13 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment.
e.printStackTrace(); e.printStackTrace();
} }
Requestor requestor = new Requestor(am, configs.getProperty("apiKey")); requestor = new Requestor(am, configs.getProperty("apiKey"));
SynchronousReceiver<Integer[]> listIdsReceiver = new SynchronousReceiver<>(); SynchronousReceiver<Integer[]> listIdsReceiver = new SynchronousReceiver<>();
SynchronousReceiver<List> listReceiver = new SynchronousReceiver<>();
requestor.getListOfIds(List.class, listIdsReceiver, listIdsReceiver); final Requestor.Receiver<Integer[]> recv = this;
try { requestor.getListOfIds(List.class, recv, null);
Integer[] listIds = listIdsReceiver.await();
for (int i = 0; i < listIds.length; i++) {
requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver);
shoppingLists.add(listReceiver.await());
}
} catch (Exception e) {
e.printStackTrace();
}
// Set adapter and display this users lists
displayShoppingListsAdapter = new DisplayShoppingListsAdapter(getActivity(), shoppingLists);
shoppingListsView.setAdapter(displayShoppingListsAdapter);
shoppingListsView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent listPage = new Intent(getContext(), ListPage.class);
// Send the list ID
listPage.putExtra("listID", shoppingLists.get(position).getItemID());
startActivity(listPage);
}
});
FloatingActionButton fab = (FloatingActionButton) root.findViewById(R.id.new_list_fab); FloatingActionButton fab = (FloatingActionButton) root.findViewById(R.id.new_list_fab);
Fragment thisFragment = this; Fragment thisFragment = this;
fab.setOnClickListener(new View.OnClickListener() { fab.setOnClickListener(new View.OnClickListener() {
@ -97,6 +86,8 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment.
@Override @Override
public void sendNewListName(String name) { public void sendNewListName(String name) {
LoadingCircleDialog loadingDialog = new LoadingCircleDialog(getActivity());
loadingDialog.show();
Properties configs = new Properties(); Properties configs = new Properties();
try { try {
@ -111,13 +102,95 @@ public class ListsFragment extends Fragment implements CreateListDialogFragment.
try { try {
requestor.postObject(newList, idReceiver, idReceiver); requestor.postObject(newList, idReceiver, idReceiver);
newList.setItemID(idReceiver.await());
shoppingLists.add(newList);
displayShoppingListsAdapter.notifyDataSetChanged();
Toast.makeText(getContext(), String.format("%s created", name), Toast.LENGTH_LONG).show();
} catch (Exception e) { } catch (Exception e) {
Toast.makeText(getContext(), "An error occurred", Toast.LENGTH_LONG).show(); Toast.makeText(getContext(), "An error occurred", Toast.LENGTH_LONG).show();
e.printStackTrace(); e.printStackTrace();
} }
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
newList.setItemID(idReceiver.await());
} catch (Exception e) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getContext(), "An error occurred", Toast.LENGTH_LONG).show();
loadingDialog.cancel();
}
});
e.printStackTrace();
}
shoppingLists.add(newList);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
displayShoppingListsAdapter.notifyDataSetChanged();
loadingDialog.cancel();
Toast.makeText(getContext(), String.format("%s created", name), Toast.LENGTH_LONG).show();
}
});
}
});
t.start();
}
@Override
public void acceptDelivery(Object delivered) {
Integer[] listIds = (Integer[]) delivered;
// Create threads and add them to a list
Thread[] threads = new Thread[listIds.length];
List[] results = new List[listIds.length];
for (int i = 0; i < listIds.length; i++) {
SynchronousReceiver<List> listReceiver = new SynchronousReceiver<>();
requestor.getObject(Integer.toString(listIds[i]), List.class, listReceiver, listReceiver);
final int finalI = i;
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
results[finalI] = listReceiver.await();
} catch (Exception e) {
e.printStackTrace();
}
}
});
threads[i] = t;
t.start();
}
// Wait for each thread to finish and add results to shoppingLists
for (int i = 0; i < threads.length; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
shoppingLists.add(results[i]);
}
// Set adapter and display this users lists
displayShoppingListsAdapter = new DisplayShoppingListsAdapter(getActivity(), shoppingLists);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
shoppingListsView.setAdapter(displayShoppingListsAdapter);
shoppingListsView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent listPage = new Intent(getContext(), ListPage.class);
// Send the list ID
listPage.putExtra("listID", shoppingLists.get(position).getItemID());
startActivity(listPage);
}
});
loadingLists.setVisibility(View.GONE);
}
});
} }
} }

View File

@ -29,11 +29,65 @@
<ListView <ListView
android:id="@+id/listView" android:id="@+id/listView"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="580dp" android:layout_height="match_parent"
android:layout_marginBottom="1dp" android:layout_marginBottom="1dp"
android:paddingBottom="20dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progress_loading_list_items"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:indeterminate="true"
android:visibility="gone"/>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listView"
android:paddingBottom="20dp">
</ListView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:background="@color/colorPrimaryDark">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="16sp"
android:text="Total: "/>
<TextView
android:id="@+id/total_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="16sp"
android:text="@string/default__00_00"/>
</LinearLayout>
</RelativeLayout>

View File

@ -12,6 +12,16 @@
tools:context=".SearchResults" tools:context=".SearchResults"
tools:showIn="@layout/activity_search_results"> tools:showIn="@layout/activity_search_results">
<ProgressBar
android:id="@+id/progress_loading_search"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:indeterminate="true"
android:visibility="gone"/>
<ListView <ListView
android:id="@+id/search_results_list" android:id="@+id/search_results_list"
android:layout_width="fill_parent" android:layout_width="fill_parent"

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null">
<ProgressBar
android:id="@+id/progress_loading_lists"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:indeterminate="true"
android:visibility="visible"/>
</RelativeLayout>

View File

@ -31,7 +31,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="100dp" android:layout_height="wrap_content"
android:paddingStart="15dp"> android:paddingStart="15dp">
<Spinner <Spinner
@ -50,4 +50,45 @@
android:src="@drawable/ic_baseline_arrow_upward_50"/> android:src="@drawable/ic_baseline_arrow_upward_50"/>
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/tv_price_label"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginTop="20dp"
android:layout_marginStart="15dp"
android:text="Price" />
<com.crystal.crystalrangeseekbar.widgets.CrystalRangeSeekbar
android:id="@+id/price_range_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_min_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="20dp"
android:layout_marginStart="15dp"
android:text="$00.00" />
<TextView
android:id="@+id/tv_max_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="20dp"
android:layout_marginEnd="15dp"
android:gravity="end"
android:text="$00.00" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -12,6 +12,16 @@
tools:context=".ui.lists.ListsFragment" tools:context=".ui.lists.ListsFragment"
tools:showIn="@layout/fragment_lists"> tools:showIn="@layout/fragment_lists">
<ProgressBar
android:id="@+id/progress_loading_lists"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:indeterminate="true"
android:visibility="gone"/>
<ListView <ListView
android:id="@+id/shopping_lists" android:id="@+id/shopping_lists"
android:layout_width="fill_parent" android:layout_width="fill_parent"