diff --git a/app/src/main/java/com/example/househomey/AddItemFragment.java b/app/src/main/java/com/example/househomey/AddItemFragment.java index 7ea57734..0bf66c85 100644 --- a/app/src/main/java/com/example/househomey/AddItemFragment.java +++ b/app/src/main/java/com/example/househomey/AddItemFragment.java @@ -20,15 +20,34 @@ import java.util.HashMap; import java.util.Objects; +/** + * This fragment is responsible for creating and loading to the database a new item + * @author Owen Cooke + */ public class AddItemFragment extends Fragment { private Date dateAcquired; private TextInputEditText dateTextView; private final CollectionReference itemRef; + /** + * Constructs a new AddItemFragment with a firestore reference + * @param itemRef A reference to a firestore collection of items + */ public AddItemFragment(CollectionReference itemRef) { this.itemRef = itemRef; } + /** + * This creates the view to add an item to a user's inventory and set's the button listeners + * @param inflater The LayoutInflater object that can be used to inflate + * any views in the fragment, + * @param container If non-null, this is the parent view that the fragment's + * UI should be attached to. The fragment should not add the view itself, + * but this can be used to generate the LayoutParams of the view. + * @param savedInstanceState If non-null, this fragment is being re-constructed + * from a previous saved state as given here. + * @return The fragment_add_item view with the correct listeners added + */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_add_item, container, false); @@ -44,6 +63,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa return rootView; } + /** + * This shows a datePicker for the user to select the date of the item acquisition + */ private void showDatePicker() { MaterialDatePicker datePicker = MaterialDatePicker.Builder.datePicker() .setInputMode(MaterialDatePicker.INPUT_MODE_CALENDAR) @@ -57,6 +79,9 @@ private void showDatePicker() { datePicker.show(getParentFragmentManager(), "Date Picker"); } + /** + * Adds the user input data to the firestore database + */ private void addItem() { // TODO: add input validation @@ -81,10 +106,19 @@ private void addItem() { }); } + /** + * Gets the user input as a string from a given TextInputEditText + * @param id Id of the TextInputEditText object + * @return The user input String + * @throws NullPointerException if the input text is null + */ private String getInputText(int id) { return Objects.requireNonNull(((TextInputEditText) requireView().findViewById(id)).getText()).toString(); } + /** + * Changes the fragment back to the home screen + */ private void returnToHomeScreen() { FragmentManager fragmentManager = getParentFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); diff --git a/app/src/main/java/com/example/househomey/FirestoreUpdateListener.java b/app/src/main/java/com/example/househomey/FirestoreUpdateListener.java deleted file mode 100644 index 54929f02..00000000 --- a/app/src/main/java/com/example/househomey/FirestoreUpdateListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.example.househomey; - -public interface FirestoreUpdateListener { - - public void notifyDataSetChanged(); -} diff --git a/app/src/main/java/com/example/househomey/HomeFragment.java b/app/src/main/java/com/example/househomey/HomeFragment.java index b7a52742..d30faedb 100644 --- a/app/src/main/java/com/example/househomey/HomeFragment.java +++ b/app/src/main/java/com/example/househomey/HomeFragment.java @@ -1,6 +1,7 @@ package com.example.househomey; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.View; @@ -19,38 +20,87 @@ import com.example.househomey.Filters.MakeFilterFragment; import com.example.househomey.Filters.TagFilterFragment; import com.google.firebase.firestore.CollectionReference; +import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.QueryDocumentSnapshot; +import com.google.firebase.firestore.QuerySnapshot; -public class HomeFragment extends Fragment implements FirestoreUpdateListener { +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * This fragment represents the home screen containing the primary list of the user's inventory + * @author Owen Cooke, Jared Drueco, Lukas Bonkowski + */ +public class HomeFragment extends Fragment { private CollectionReference itemRef; private ListView itemListView; private PopupMenu filterView; + + private ArrayList itemList = new ArrayList<>(); private ArrayAdapter itemAdapter; + /** + * This constructs a new HomeFragment with the appropriate list of items + * @param itemRef A reference to the firestore collection containing the items to display + */ public HomeFragment(CollectionReference itemRef) { this.itemRef = itemRef; } + /** + * @param inflater The LayoutInflater object that can be used to inflate + * any views in the fragment, + * @param container If non-null, this is the parent view that the fragment's + * UI should be attached to. The fragment should not add the view itself, + * but this can be used to generate the LayoutParams of the view. + * @param savedInstanceState If non-null, this fragment is being re-constructed + * from a previous saved state as given here. + * @return the home fragment view containing the inventory list + */ @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // Inflate the fragment's layout View rootView = inflater.inflate(R.layout.fragment_home, container, false); itemListView = rootView.findViewById(R.id.item_list); - itemAdapter = new ItemAdapter(getContext(), new ItemList(this, itemRef).getItems()); + itemAdapter = new ItemAdapter(getContext(), itemList); itemListView.setAdapter(itemAdapter); + itemRef.addSnapshotListener(this::setupItemListener); + View filterButton = rootView.findViewById(R.id.filter_dropdown_button); - filterButton.setOnClickListener(v -> showFilterMenu(v)); + filterButton.setOnClickListener(this::showFilterMenu); return rootView; } - @Override - public void notifyDataSetChanged() { - itemAdapter.notifyDataSetChanged(); + /** + * This method updates the itemAdapter with changes in the firestore database and creates new + * item objects + * @param querySnapshots The updated information on the inventory from the database + * @param error Non-null if an error occurred in Firestore + */ + private void setupItemListener(QuerySnapshot querySnapshots, FirebaseFirestoreException error) { + if (error != null) { + Log.e("Firestore", error.toString()); + return; + } + if (querySnapshots != null) { + itemList.clear(); + for (QueryDocumentSnapshot doc: querySnapshots) { + Map data = new HashMap<>(doc.getData()); + itemList.add(new Item(doc.getId(), data)); + itemAdapter.notifyDataSetChanged(); + } + } } + /** + * Displays the filter menu with options to select the appropriate filter + * @param view The view to set the filter menu on + */ private void showFilterMenu(View view) { PopupMenu popupMenu = new PopupMenu(requireContext(), view); MenuInflater inflater = popupMenu.getMenuInflater(); diff --git a/app/src/main/java/com/example/househomey/Item.java b/app/src/main/java/com/example/househomey/Item.java index f1e005ac..dec8687e 100644 --- a/app/src/main/java/com/example/househomey/Item.java +++ b/app/src/main/java/com/example/househomey/Item.java @@ -1,12 +1,13 @@ package com.example.househomey; +import androidx.annotation.NonNull; + import com.google.firebase.Timestamp; import java.math.BigDecimal; -import java.time.LocalDateTime; import java.util.Date; -import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * This class represents an inventory item with a variety of properties @@ -17,21 +18,26 @@ public class Item { public String id; private String description; private Date acquisitionDate; - private String make; - private String model; - private String serialNumber; - private String comment; - + private String make = ""; + private String model = ""; + private String serialNumber = ""; + private String comment = ""; private BigDecimal cost; - private Map data; - - public Item(String id, Map data) { - this.data = data; - this.id = id; - this.description = (String) data.get("description"); - this.acquisitionDate = ((Timestamp) data.get("acquisitionDate")).toDate(); - + /** + * This constructs a new item from a Map of data with a reference to it's firestore docunebt + * @param id The id of this object's document in the firestore database + * @param data The data from that document to initialize the instance + * @throws NullPointerException if a null required field is given + */ + public Item(String id, @NonNull Map data) { + // Required fields + this.id = Objects.requireNonNull(id); + this.description = (String) Objects.requireNonNull(data.get("description")); + this.acquisitionDate = ((Timestamp) Objects.requireNonNull(data.get("acquisitionDate"))).toDate(); + this.cost = new BigDecimal((String) Objects.requireNonNull(data.get("cost"))).setScale(2); + + // Optional fields if (data.containsKey("make")) { this.make = (String) data.get("make"); } @@ -44,22 +50,29 @@ public Item(String id, Map data) { if (data.containsKey("comment")) { this.comment = (String) data.get("comment"); } - - this.cost = new BigDecimal((String) data.get("cost")).setScale(2); - } - - public HashMap data() { - return new HashMap<>(); } + /** + * Getter for acquisitionDate + * @return The acquisition date of this item + */ public Date getAcquisitionDate() { return acquisitionDate; } + /** + * Getter for description + * @return The brief description of this item + */ public String getDescription() { return description; } + + /** + * Getter for cost + * @return The cost of this item + */ public BigDecimal getCost() { return cost; } diff --git a/app/src/main/java/com/example/househomey/ItemAdapter.java b/app/src/main/java/com/example/househomey/ItemAdapter.java index a9accbcb..b3483bcd 100644 --- a/app/src/main/java/com/example/househomey/ItemAdapter.java +++ b/app/src/main/java/com/example/househomey/ItemAdapter.java @@ -34,12 +34,31 @@ public class ItemAdapter extends ArrayAdapter { private ArrayList items; private Context context; + /** + * Constructs a new ItemAdapter with an ArrayList of items + * @param context The context containing this adapter + * @param items ArrayList of items to display + */ public ItemAdapter(Context context, ArrayList items){ super(context, 0, items); this.items = items; this.context = context; } + + /** + * Gets a view to display an items from this adapters ArrayList. + * @param position The position of the item within the adapter's data set of the item whose view + * we want. + * @param convertView The old view to reuse, if possible. Note: You should check that this view + * is non-null and of an appropriate type before using. If it is not possible to convert + * this view to display the correct data, this method can create a new view. + * Heterogeneous lists can specify their number of view types, so that this View is + * always of the right type (see {@link #getViewTypeCount()} and + * {@link #getItemViewType(int)}). + * @param parent The parent that this view will eventually be attached to + * @return The view with all data from the items displayed + */ @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { diff --git a/app/src/main/java/com/example/househomey/ItemList.java b/app/src/main/java/com/example/househomey/ItemList.java deleted file mode 100644 index 0bdc655c..00000000 --- a/app/src/main/java/com/example/househomey/ItemList.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.example.househomey; - -import android.content.Context; -import android.util.Log; - -import com.google.firebase.firestore.CollectionReference; -import com.google.firebase.firestore.DocumentReference; -import com.google.firebase.firestore.FirebaseFirestore; -import com.google.firebase.firestore.QueryDocumentSnapshot; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -public class ItemList { - - private ArrayList items = new ArrayList<>(); - - private CollectionReference itemsRef; - private FirestoreUpdateListener listener; - - public ItemList(FirestoreUpdateListener listener, CollectionReference itemsRef) { - this.listener = listener; - this.itemsRef = itemsRef; - initItems(); - } - - private void initItems() { - itemsRef.addSnapshotListener((querySnapshots, error) -> { - if (error != null) { - Log.e("Firestore", error.toString()); - return; - } - if (querySnapshots != null) { - items.clear(); - for (QueryDocumentSnapshot doc: querySnapshots) { - Map data = new HashMap<>(); - data.putAll(doc.getData()); - items.add(new Item(doc.getId(), data)); - listener.notifyDataSetChanged(); - } - } - }); - } - - public ArrayList getItems() { - return items; - } -} diff --git a/app/src/main/java/com/example/househomey/MainActivity.java b/app/src/main/java/com/example/househomey/MainActivity.java index ceb57269..01d996c0 100644 --- a/app/src/main/java/com/example/househomey/MainActivity.java +++ b/app/src/main/java/com/example/househomey/MainActivity.java @@ -20,9 +20,22 @@ import com.google.firebase.firestore.CollectionReference; import com.google.firebase.firestore.FirebaseFirestore; + +/** + * MainActivity of the application, handles setting up the bottom nav fragment and the user + * @author Owen Cooke, Lukas Bonkowski + */ public class MainActivity extends AppCompatActivity { // Define constants for filter items private User user; + + /** + * Method to run on creation of the activity. Handles user setup and creates the bottom + * nav fragment + * @param savedInstanceState If the activity is being re-initialized after + * previously being shut down then this Bundle contains the data it most + * recently supplied in {@link #onSaveInstanceState}. Note: Otherwise it is null. + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -53,12 +66,16 @@ protected void onCreate(Bundle savedInstanceState) { }); } + /** + * Changes the current fragment to the passed fragment + * @param fragment a Fragment to navigate to and replace the current fragment with + */ private void navigateToFragment(Fragment fragment) { // Get the FragmentManager and start a transaction FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction(); - // Replace the current fragment with the addItemFragment + // Replace the current fragment with the new Fragment transaction.replace(R.id.fragmentContainer, fragment); // Commit the transaction diff --git a/app/src/main/java/com/example/househomey/User.java b/app/src/main/java/com/example/househomey/User.java index f19c9caa..e9372482 100644 --- a/app/src/main/java/com/example/househomey/User.java +++ b/app/src/main/java/com/example/househomey/User.java @@ -4,12 +4,21 @@ import com.google.firebase.firestore.DocumentReference; import com.google.firebase.firestore.FirebaseFirestore; +/** + * This class represents a user and gets references to the appropriate information from firestore + * @author Lukas Bonkowski, Owen Cooke + */ public class User { private String username; private FirebaseFirestore db; private DocumentReference userRef; private CollectionReference itemRef; + /** + * This constructs a new user getting references to their firestore information + * @param username The unique username for this user to reference in firestore to find their + * item collection + */ public User(String username) { db = FirebaseFirestore.getInstance(); this.username = username; @@ -17,14 +26,10 @@ public User(String username) { itemRef = userRef.collection("item"); } - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - + /** + * Getter for itemRef + * @return A reference to the user's firestore item collection + */ public CollectionReference getItemRef() { return itemRef; } diff --git a/app/src/test/java/com/example/househomey/ExampleUnitTest.java b/app/src/test/java/com/example/househomey/ExampleUnitTest.java deleted file mode 100644 index 7a4b7fad..00000000 --- a/app/src/test/java/com/example/househomey/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.househomey; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/app/src/test/java/com/example/househomey/ItemTest.java b/app/src/test/java/com/example/househomey/ItemTest.java new file mode 100644 index 00000000..18b38dbe --- /dev/null +++ b/app/src/test/java/com/example/househomey/ItemTest.java @@ -0,0 +1,84 @@ +package com.example.househomey; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import com.google.firebase.Timestamp; + +import org.junit.Before; +import org.junit.Test; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class ItemTest { + private final Date startDate = new Date(); + private final String id = "test"; + private Map inputMap; + + private Map requiredInputMap() { + Map inputMap = new HashMap<>(); + inputMap.put("description", "Glass"); + inputMap.put("acquisitionDate", new Timestamp(startDate)); + inputMap.put("cost", "47.00"); + return inputMap; + } + + @Before + public void setUp() { + inputMap = requiredInputMap(); + } + @Test + public void testRequiredConstructor() { + Item newItem = new Item(id, inputMap); + + assertEquals(id, newItem.id); + assertEquals(inputMap.get("description"), newItem.getDescription()); + assertEquals(((Timestamp)inputMap.get("acquisitionDate")).toDate(), newItem.getAcquisitionDate()); + assertEquals(new BigDecimal((String) inputMap.get("cost")), newItem.getCost()); + assertEquals("", newItem.getMake()); + assertEquals("", newItem.getModel()); + assertEquals("", newItem.getSerialNumber()); + assertEquals("", newItem.getComment()); + } + + @Test + public void testAllFieldsConstructor() { + inputMap.put("make", "Mikasa"); + inputMap.put("model", "Tall glass"); + inputMap.put("serialNumber", "123456"); + inputMap.put("comment", "chipped"); + Item newItem = new Item(id, inputMap); + + assertEquals(inputMap.get("make"), newItem.getMake()); + assertEquals(inputMap.get("model"), newItem.getModel()); + assertEquals(inputMap.get("serialNumber"), newItem.getSerialNumber()); + assertEquals(inputMap.get("comment"), newItem.getComment()); + } + + @Test + public void testMissingId() { + assertThrows(NullPointerException.class, () -> new Item(null, inputMap)); + } + + @Test + public void testMissingDescription() { + inputMap.remove("description"); + assertThrows(NullPointerException.class, () -> new Item(id, inputMap)); + } + + @Test + public void testMissingAcquisitionDate() { + inputMap.remove("acquisitionDate"); + assertThrows(NullPointerException.class, () -> new Item(id, inputMap)); + } + + @Test + public void testMissingCost() { + inputMap.remove("cost"); + assertThrows(NullPointerException.class, () -> new Item(id, inputMap)); + } + +}