diff --git a/app/src/androidTest/java/com/example/househomey/AddItemFragmentTest.java b/app/src/androidTest/java/com/example/househomey/AddItemFragmentTest.java index 4d3a2656..2a4c649e 100644 --- a/app/src/androidTest/java/com/example/househomey/AddItemFragmentTest.java +++ b/app/src/androidTest/java/com/example/househomey/AddItemFragmentTest.java @@ -9,13 +9,13 @@ import static androidx.test.espresso.matcher.ViewMatchers.hasChildCount; import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static com.example.househomey.testUtils.TestHelpers.enterText; import static com.example.househomey.testUtils.TestHelpers.hasListLength; import static com.example.househomey.testUtils.TestHelpers.mockImageBitmap; import static com.example.househomey.testUtils.TestHelpers.mockImageUri; +import static com.example.househomey.testUtils.TestHelpers.pickDate; import static com.example.househomey.testUtils.TestHelpers.waitFor; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.anything; @@ -27,46 +27,26 @@ import androidx.test.espresso.intent.Intents; import androidx.test.espresso.intent.matcher.IntentMatchers; -import androidx.test.espresso.matcher.RootMatchers; import com.example.househomey.testUtils.TestSetup; import org.junit.Before; import org.junit.Test; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; - public class AddItemFragmentTest extends TestSetup { @Before public void navigateToAddItemFragment() { onView(withId(R.id.action_add)).perform(click()); } - public String selectFirstDayOfMonth() { - onView(withId(R.id.add_item_date)).perform(click()); - // Get the first day of the month matching MaterialDatePicker content description format - LocalDate currentDate = LocalDate.now().withDayOfMonth(1); - DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("EEEE, MMMM d"); - String formattedDate = currentDate.format(dateFormat); - // Select the day and click confirm - onView(withContentDescription(formattedDate)) - .inRoot(RootMatchers.isDialog()) - .perform(click()); - onView(withId(com.google.android.material.R.id.confirm_button)).perform(click()); - // Return HomePage's formatted date - return currentDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); - } - @Test public void testAddItemWithNewUser() { - // Add acquisition date - String acquisitionDate = selectFirstDayOfMonth(); - // Add required description and estimated cost String itemDescription = "Test Item"; String estimatedCost = "99.99"; String itemMake = "MyMake"; + // Add required fields enterText(R.id.add_item_description, itemDescription); + pickDate(R.id.add_item_date, "10/01/2023"); enterText(R.id.add_item_cost, estimatedCost); // Add values to other non-required fields enterText(R.id.add_item_make, itemMake); @@ -87,7 +67,7 @@ public void testAddItemWithNewUser() { .inAdapterView(withId(R.id.item_list)) .atPosition(0) .onChildView(withId(R.id.item_date_text)) - .check(matches(withText(acquisitionDate))); + .check(matches(withText("2023-10-01"))); onData(anything()) .inAdapterView(withId(R.id.item_list)) .atPosition(0) @@ -126,7 +106,6 @@ public void testRoundCostTo2Decimals() { @Test public void testAddPhotoFromCamera() { // Mock a result for the system's camera - Intents.init(); Intent resultData = new Intent(); resultData.putExtra("data", mockImageBitmap(mainActivity, R.raw.classic_guitar)); Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData); @@ -139,7 +118,6 @@ public void testAddPhotoFromCamera() { // Click the camera option and ensure intent was fired onView(withId(R.id.camera_button)).perform(click()); intended(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)); - Intents.release(); // Check that the photo was added onView(withId(R.id.add_photo_grid)).check(matches(hasChildCount(2))); @@ -149,7 +127,6 @@ public void testAddPhotoFromCamera() { @Test public void testAddPhotoFromGallery() { // Mock a result for the system's gallery - Intents.init(); Intent resultData = new Intent(); resultData.setData(mockImageUri(R.raw.shoes)); Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData); @@ -162,7 +139,6 @@ public void testAddPhotoFromGallery() { // Click the gallery option and ensure intent was fired onView(withId(R.id.gallery_button)).perform(click()); intended(hasAction(Intent.ACTION_PICK)); - Intents.release(); // Check that the photo was added onView(withId(R.id.add_photo_grid)).check(matches(hasChildCount(2))); diff --git a/app/src/androidTest/java/com/example/househomey/BarcodeScannerTest.java b/app/src/androidTest/java/com/example/househomey/BarcodeScannerTest.java index f0b0b6ed..bf948fb4 100644 --- a/app/src/androidTest/java/com/example/househomey/BarcodeScannerTest.java +++ b/app/src/androidTest/java/com/example/househomey/BarcodeScannerTest.java @@ -33,7 +33,6 @@ public void navigateToAddItemFragment() { @Test public void testSetSerialNumber() { // Mock a result for the system's gallery - Intents.init(); Intent resultData = new Intent(); resultData.setData(mockImageUri(R.raw.barcode_test)); Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData); @@ -46,7 +45,6 @@ public void testSetSerialNumber() { // Click the gallery option and ensure intent was fired onView(withId(R.id.gallery_button)).perform(click()); intended(hasAction(Intent.ACTION_PICK)); - Intents.release(); waitFor(() -> onView(withText("Serial Number")).perform(click())); @@ -57,7 +55,6 @@ public void testSetSerialNumber() { @Test public void testSetDescription() { // Mock a result for the system's gallery - Intents.init(); Intent resultData = new Intent(); resultData.setData(mockImageUri(R.raw.barcode_test)); Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData); @@ -70,7 +67,6 @@ public void testSetDescription() { // Click the gallery option and ensure intent was fired onView(withId(R.id.gallery_button)).perform(click()); intended(hasAction(Intent.ACTION_PICK)); - Intents.release(); waitFor(() -> onView(withText("Description")).perform(click())); @@ -81,7 +77,6 @@ public void testSetDescription() { @Test public void testQRCodeScan() { // Mock a result for the system's gallery - Intents.init(); Intent resultData = new Intent(); resultData.setData(mockImageUri(R.raw.househomey_qrcode)); Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData); @@ -94,7 +89,6 @@ public void testQRCodeScan() { // Click the gallery option and ensure intent was fired onView(withId(R.id.gallery_button)).perform(click()); intended(hasAction(Intent.ACTION_PICK)); - Intents.release(); waitFor(() -> onView(withText("YES")).perform(click())); diff --git a/app/src/androidTest/java/com/example/househomey/DateFilterFragmentTest.java b/app/src/androidTest/java/com/example/househomey/DateFilterFragmentTest.java index b2e024a6..e5c1d5ab 100644 --- a/app/src/androidTest/java/com/example/househomey/DateFilterFragmentTest.java +++ b/app/src/androidTest/java/com/example/househomey/DateFilterFragmentTest.java @@ -1,55 +1,46 @@ package com.example.househomey; -import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; -import static androidx.test.espresso.action.ViewActions.replaceText; -import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static com.example.househomey.testUtils.TestHelpers.hasListLength; +import static com.example.househomey.testUtils.TestHelpers.pickDate; import static com.example.househomey.testUtils.TestHelpers.waitFor; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.Matchers; -import org.hamcrest.TypeSafeMatcher; + +import com.example.househomey.testUtils.TestSetup; + import org.junit.Before; import org.junit.Test; -import com.example.househomey.testUtils.TestSetup; public class DateFilterFragmentTest extends TestSetup { @Before public void waitForItems() { - waitFor(() -> { - onView(withId(R.id.item_list)).check(matches(isDisplayed())); - }); + waitFor(() -> onView(withId(R.id.item_list)).check(matches(isDisplayed()))); navigateToDateRangeFilter(); } @Test public void testDateFilterWithResults() { - setDateFilter(R.id.start_date_filter, "10/01/2023"); - setDateFilter(R.id.end_date_filter, "10/30/2023"); + pickDate(R.id.start_date_filter, "10/01/2023"); + pickDate(R.id.end_date_filter, "10/30/2023"); onView(withText("APPLY")).perform(click()); waitFor(() -> hasListLength(3)); } @Test public void testDateFilterWithNoResults() { - setDateFilter(R.id.start_date_filter, "10/01/2001"); - setDateFilter(R.id.end_date_filter, "10/01/2001"); + pickDate(R.id.start_date_filter, "10/01/2001"); + pickDate(R.id.end_date_filter, "10/01/2001"); onView(withText("APPLY")).perform(click()); waitFor(() -> hasListLength(0)); } @Test public void testDateFilterWithOneDateFilled() { - setDateFilter(R.id.start_date_filter, "10/01/2023"); + pickDate(R.id.start_date_filter, "10/01/2023"); onView(withText("APPLY")).perform(click()); waitFor(() -> hasListLength(4)); } @@ -63,52 +54,4 @@ public void navigateToDateRangeFilter() { onView(withText("Date Range")).perform(click()); } - /** - * Sets a date filter for a specified view element using Espresso. - * - * @param id The resource ID of the view element for which the date filter is being set. - * @param date The date string to be set in the date filter. - * This method clicks on the specified view element, switches to text input mode, - * enters the provided date, and confirms the date selection by clicking "OK". - */ - private void setDateFilter(int id, String date) { - onView(withId(id)).perform(click()); - onView(withContentDescription("Switch to text input mode")).perform(click()); - onView( - Matchers.allOf(childAtPosition( - childAtPosition( - withId(com.google.android.material.R.id.mtrl_picker_text_input_date), - 0), - 0), - isDisplayed())).perform(replaceText(date), closeSoftKeyboard()); - - onView(withText("OK")).perform(click()); - } - - /** - * Custom Matcher for locating a child {@link View} at a specific position within a parent - * {@link ViewGroup}. - * - * @param parentMatcher The {@link Matcher} used to match the parent {@link View} or {@link ViewGroup}. - * @param position The position of the child {@link View} within the parent. - * @return A {@link Matcher} instance for finding a child {@link View} at the specified position. - */ - Matcher childAtPosition( - final Matcher parentMatcher, final int position) { - - return new TypeSafeMatcher() { - @Override - public void describeTo(Description description) { - description.appendText("Child at position " + position + " in parent "); - parentMatcher.describeTo(description); - } - - @Override - public boolean matchesSafely(View view) { - ViewParent parent = view.getParent(); - return parent instanceof ViewGroup && parentMatcher.matches(parent) - && view.equals(((ViewGroup) parent).getChildAt(position)); - } - }; - } } diff --git a/app/src/androidTest/java/com/example/househomey/SerialNumScannerTest.java b/app/src/androidTest/java/com/example/househomey/SerialNumScannerTest.java index 8df34f0a..f6612403 100644 --- a/app/src/androidTest/java/com/example/househomey/SerialNumScannerTest.java +++ b/app/src/androidTest/java/com/example/househomey/SerialNumScannerTest.java @@ -36,7 +36,6 @@ public void navigateToAddItemFragment() { @Test public void testScanPhoto() { // Mock a result for the system's gallery - Intents.init(); Intent resultData = new Intent(); resultData.setData(mockImageUri(R.raw.serial_num)); Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData); @@ -49,7 +48,6 @@ public void testScanPhoto() { // Click the gallery option and ensure intent was fired onView(withId(R.id.gallery_button)).perform(click()); intended(hasAction(Intent.ACTION_PICK)); - Intents.release(); waitFor(() -> onView(withText("YES")).perform(click())); diff --git a/app/src/androidTest/java/com/example/househomey/testUtils/TestHelpers.java b/app/src/androidTest/java/com/example/househomey/testUtils/TestHelpers.java index a18e9e6b..ad817d3a 100644 --- a/app/src/androidTest/java/com/example/househomey/testUtils/TestHelpers.java +++ b/app/src/androidTest/java/com/example/househomey/testUtils/TestHelpers.java @@ -2,11 +2,15 @@ import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.clearText; +import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; import static androidx.test.espresso.action.ViewActions.pressImeActionButton; +import static androidx.test.espresso.action.ViewActions.replaceText; import static androidx.test.espresso.action.ViewActions.scrollTo; import static androidx.test.espresso.action.ViewActions.typeText; import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; @@ -14,11 +18,19 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; import androidx.annotation.IdRes; import com.example.househomey.R; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.hamcrest.TypeSafeMatcher; + /* * Helper methods for Espresso testing in Android applications. * @@ -97,4 +109,46 @@ public static Bitmap mockImageBitmap(Activity activity, int resourceId) { public static Uri mockImageUri(int resourceId) { return Uri.parse("android.resource://com.example.househomey/" + resourceId); } + + /* + * Sets the Material DatePicker date for a specified date text input. + * Uses text input mode of the DatePicker. + * + * @param id The resource ID of the view element for which the date is being set. + * @param date The date string to be set. + */ + public static void pickDate(int id, String date) { + onView(withId(id)).perform(click()); + onView(withContentDescription("Switch to text input mode")).perform(click()); + onView(Matchers.allOf(childAtPosition(childAtPosition( + withId(com.google.android.material.R.id.mtrl_picker_text_input_date), 0), 0), + isDisplayed())).perform(replaceText(date), closeSoftKeyboard()); + onView(withText("OK")).perform(click()); + } + + /* + * Custom Matcher for locating a child at a specific position within a parent + * + * @param parentMatcher The Matcher used to match the parent View or ViewGroup + * @param position The position of the child View within the parent. + * @return A Matcher instance for finding a child View at the specified position. + */ + public static Matcher childAtPosition( + final Matcher parentMatcher, final int position) { + + return new TypeSafeMatcher() { + @Override + public void describeTo(Description description) { + description.appendText("Child at position " + position + " in parent "); + parentMatcher.describeTo(description); + } + + @Override + public boolean matchesSafely(View view) { + ViewParent parent = view.getParent(); + return parent instanceof ViewGroup && parentMatcher.matches(parent) + && view.equals(((ViewGroup) parent).getChildAt(position)); + } + }; + } } diff --git a/app/src/androidTest/java/com/example/househomey/testUtils/TestSetup.java b/app/src/androidTest/java/com/example/househomey/testUtils/TestSetup.java index 7b985e4c..eaa31b99 100644 --- a/app/src/androidTest/java/com/example/househomey/testUtils/TestSetup.java +++ b/app/src/androidTest/java/com/example/househomey/testUtils/TestSetup.java @@ -2,6 +2,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import androidx.test.espresso.intent.Intents; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.UiObject; @@ -10,8 +11,10 @@ import com.example.househomey.MainActivity; +import org.junit.After; import org.junit.Before; import org.junit.Rule; +import org.junit.Test; import java.io.IOException; @@ -51,5 +54,11 @@ public void dismissAppCrashSystemDialogIfShown() { @Before public void setup() { mainActivity = database.setupActivity(); + Intents.init(); + } + + @After + public void tearDown() { + Intents.release(); } } diff --git a/app/src/main/java/com/example/househomey/HomeFragment.java b/app/src/main/java/com/example/househomey/HomeFragment.java index 1dde0480..90c95dc0 100644 --- a/app/src/main/java/com/example/househomey/HomeFragment.java +++ b/app/src/main/java/com/example/househomey/HomeFragment.java @@ -33,6 +33,7 @@ import com.example.househomey.sort.DateComparator; import com.example.househomey.sort.DescriptionComparator; import com.example.househomey.sort.MakeComparator; +import com.example.househomey.sort.TagComparator; import com.example.househomey.tags.Tag; import com.google.firebase.firestore.CollectionReference; import com.google.firebase.firestore.FirebaseFirestoreException; @@ -108,6 +109,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c sortProperties.put("date",new DateComparator()); sortProperties.put("make", new MakeComparator()); sortProperties.put("cost", new CostComparator()); + sortProperties.put("tag", new TagComparator()); Bundle received_args = getArguments(); if (received_args!=null){ @@ -149,9 +151,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c //Sort dropdown functionality final Button sortButton = rootView.findViewById(R.id.sort_by_alpha_button); - sortButton.setOnClickListener(v -> { - showSortMenu(sortButton); - }); + sortButton.setOnClickListener(v -> showSortMenu(sortButton)); //Toggle sorting order functionality toggleOrder = rootView.findViewById(R.id.sort_order_toggle); @@ -165,6 +165,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c } + /** + * This method updates the tags with changes in the firestore database and creates new + * tag objects + * @param querySnapshots The updated information on the inventory from the database + * @param error Non-null if an error occurred in Firestore + */ private void setupTagListener(QuerySnapshot querySnapshots, FirebaseFirestoreException error) { if (error != null) { Log.e("Firestore", error.toString()); @@ -358,6 +364,8 @@ private void showSortMenu(View view) { currentSortName = "make"; } else if (itemId == R.id.sort_by_estimatedValue) { currentSortName = "cost"; + } else if (itemId == R.id.sort_by_tag) { + currentSortName = "tag"; } else { return false; } diff --git a/app/src/main/java/com/example/househomey/Item.java b/app/src/main/java/com/example/househomey/Item.java index 893305de..64630977 100644 --- a/app/src/main/java/com/example/househomey/Item.java +++ b/app/src/main/java/com/example/househomey/Item.java @@ -6,14 +6,11 @@ import androidx.annotation.NonNull; -import com.example.househomey.sort.TagComparator; import com.example.househomey.tags.Tag; import com.google.firebase.Timestamp; import com.google.firebase.firestore.CollectionReference; import com.google.firebase.firestore.QueryDocumentSnapshot; -import org.checkerframework.common.returnsreceiver.qual.This; - import java.io.Serializable; import java.math.BigDecimal; import java.math.RoundingMode; @@ -41,7 +38,7 @@ public class Item implements Serializable, Parcelable { private String comment = ""; private BigDecimal cost; private List photoIds = new ArrayList<>(); - private Set tags = new TreeSet<>(new TagComparator()); + private Set tags = new TreeSet<>(); private boolean checked = false; /** @@ -335,7 +332,7 @@ protected Item(Parcel in) { this.comment = in.readString(); this.cost = new BigDecimal(in.readString()).setScale(2, RoundingMode.HALF_UP); in.readStringList(this.photoIds); - this.tags = new TreeSet<>(new TagComparator()); + this.tags = new TreeSet<>(); List tagList = new ArrayList<>(); in.readList(tagList, Tag.class.getClassLoader(), Tag.class); this.tags.addAll(tagList); diff --git a/app/src/main/java/com/example/househomey/SelectFragment.java b/app/src/main/java/com/example/househomey/SelectFragment.java index 374de07a..4159a176 100644 --- a/app/src/main/java/com/example/househomey/SelectFragment.java +++ b/app/src/main/java/com/example/househomey/SelectFragment.java @@ -20,7 +20,7 @@ import androidx.fragment.app.Fragment; import com.example.househomey.tags.Tag; -import com.example.househomey.tags.TagFragment; +import com.example.househomey.tags.ApplyTagFragment; import com.google.firebase.firestore.CollectionReference; import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.FirebaseFirestoreException; @@ -116,13 +116,13 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c final Button actionTagsButton = rootView.findViewById(R.id.action_tags); actionTagsButton.setOnClickListener(v -> { - TagFragment tagFragment = new TagFragment(); + ApplyTagFragment applyTagFragment = new ApplyTagFragment(); ArrayList selectedItems = getSelectedItems(); Bundle tagArgs = new Bundle(); tagArgs.putParcelableArrayList("itemList", selectedItems); - tagFragment.setArguments(tagArgs); - tagFragment.show(requireActivity().getSupportFragmentManager(),"tagDialog"); + applyTagFragment.setArguments(tagArgs); + applyTagFragment.show(requireActivity().getSupportFragmentManager(),"tagDialog"); }); return rootView; @@ -134,6 +134,12 @@ private void createItemIdMap() { } } + /** + * This method updates the tags with changes in the firestore database and creates new + * tag objects + * @param querySnapshots The updated information on the inventory from the database + * @param error Non-null if an error occurred in Firestore + */ private void setupTagListener(QuerySnapshot querySnapshots, FirebaseFirestoreException error) { if (error != null) { Log.e("Firestore", error.toString()); @@ -198,15 +204,9 @@ public String DialogTitle(ArrayList selectedItems) { * @return List of selected items */ public ArrayList getSelectedItems() { - ArrayList selectedItems = new ArrayList<>(); - for (int i = 0; i< itemList.size(); i++) { - View itemView = itemListView.getChildAt(i); - if (itemView!=null) { - CheckBox checkBox = itemView.findViewById(R.id.item_checkBox); - if (checkBox.isChecked()) selectedItems.add(itemList.get(i)); - } - } - return selectedItems; + return itemList.stream() + .filter(Item::getChecked) + .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); } /** diff --git a/app/src/main/java/com/example/househomey/ViewItemFragment.java b/app/src/main/java/com/example/househomey/ViewItemFragment.java index 71e91f46..8c70d32b 100644 --- a/app/src/main/java/com/example/househomey/ViewItemFragment.java +++ b/app/src/main/java/com/example/househomey/ViewItemFragment.java @@ -6,9 +6,11 @@ import android.annotation.SuppressLint; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; @@ -19,14 +21,18 @@ import com.example.househomey.form.ViewPhotoAdapter; import com.example.househomey.tags.Tag; +import com.example.househomey.tags.ApplyTagFragment; import com.example.househomey.utils.FragmentUtils; import com.google.android.material.chip.Chip; import com.google.android.material.chip.ChipGroup; import com.google.firebase.firestore.CollectionReference; +import com.google.firebase.firestore.FirebaseFirestoreException; +import com.google.firebase.firestore.QueryDocumentSnapshot; +import com.google.firebase.firestore.QuerySnapshot; import java.util.ArrayList; import java.util.List; -import java.util.Set; +import java.util.Objects; /** * This fragment is for the "View Item Page" - which currently displays the details and comment linked @@ -36,6 +42,8 @@ public class ViewItemFragment extends Fragment { private Item item; protected ViewPhotoAdapter viewPhotoAdapter; + private CollectionReference tagRef; + private ChipGroup chipGroup; /** * @@ -52,7 +60,8 @@ public class ViewItemFragment extends Fragment { @SuppressLint("SetTextI18n") public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_view_item, container, false); - + tagRef = ((MainActivity) requireActivity()).getTagRef(); + tagRef.addSnapshotListener(this::setupTagListener); // Initialize TextViews TextView title = rootView.findViewById(R.id.view_item_title); TextView make = rootView.findViewById(R.id.view_item_make); @@ -62,6 +71,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa TextView comment = rootView.findViewById(R.id.view_item_comment); TextView noPhotosView = rootView.findViewById(R.id.view_item_no_photos); ImageView mainPhoto = rootView.findViewById(R.id.view_item_main_photo); + chipGroup = rootView.findViewById(R.id.tag_chip_group_labels); // Set TextViews to Item details sent over from ItemAdapter ofNullable(getArguments()) @@ -73,7 +83,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa model.setText(item.getModel()); serialNumber.setText(item.getSerialNumber()); cost.setText(item.getCost().toString()); - addTags(item.getTags(), rootView); + addTagsToChipGroup(); comment.setText(item.getComment()); }); @@ -101,26 +111,58 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa } ((RecyclerView) rootView.findViewById(R.id.view_photo_grid)).setAdapter(viewPhotoAdapter); + final Button addTagsButton = rootView.findViewById(R.id.add_tags_button); + addTagsButton.setOnClickListener(v -> { + ApplyTagFragment applyTagFragment = new ApplyTagFragment(); + + ArrayList selectedItem = new ArrayList<>(); + selectedItem.add(item); + Bundle tagArgs = new Bundle(); + tagArgs.putParcelableArrayList("itemList", selectedItem); + applyTagFragment.setArguments(tagArgs); + applyTagFragment.show(requireActivity().getSupportFragmentManager(),"tagDialog"); + }); + return rootView; } + /** + * This method updates the tags with changes in the firestore database and creates new + * tag objects + * @param querySnapshots The updated information on the inventory from the database + * @param error Non-null if an error occurred in Firestore + */ + private void setupTagListener(QuerySnapshot querySnapshots, FirebaseFirestoreException error) { + if (error != null) { + Log.e("Firestore", error.toString()); + return; + } + if (querySnapshots != null) { + item.clearTags(); + chipGroup.removeAllViews(); + for (QueryDocumentSnapshot doc: querySnapshots) { + Tag tag = new Tag(doc.getId(), doc.getData()); + for (String id : tag.getItemIds()) { + if (Objects.equals(item.getId(), id)) item.addTag(tag); + } + } + addTagsToChipGroup(); + } + } + /** * Adds tags such that they can be viewed on the view item page. Also enables tags to be deleted when clicking the close icon. - * @param tagList list of tags to be added - * @param rootView the root view of the view item page */ - private void addTags(Set tagList, View rootView) { - ChipGroup chipGroup = rootView.findViewById(R.id.tag_chip_group_labels); + private void addTagsToChipGroup() { CollectionReference tagRef = ((MainActivity) requireActivity()).getTagRef(); - for (Tag tag: tagList) { - final Chip chip = FragmentUtils.makeChip(tag.getTagLabel(), true, chipGroup, rootView.getContext(), R.color.creme, R.color.black, R.color.black); + for (Tag tag: item.getTags()) { + final Chip chip = FragmentUtils.makeChip(tag.getTagLabel(), true, chipGroup, getContext(), R.color.creme, R.color.black, R.color.black); final Tag finalTag = tag; chip.setOnCloseIconClickListener(v -> { tagRef.document(finalTag.getTagLabel()).get().addOnCompleteListener(task -> { if (task.isSuccessful()) { ArrayList itemIds = new ArrayList<>((List) task.getResult().get("items")); itemIds.removeIf(item -> item.equals(this.item.getId())); - chipGroup.removeView(chip); tagRef.document(finalTag.getTagLabel()).update("items", itemIds); } }); diff --git a/app/src/main/java/com/example/househomey/sort/TagComparator.java b/app/src/main/java/com/example/househomey/sort/TagComparator.java index fd5c62bc..f8ba6bbe 100644 --- a/app/src/main/java/com/example/househomey/sort/TagComparator.java +++ b/app/src/main/java/com/example/househomey/sort/TagComparator.java @@ -1,12 +1,40 @@ package com.example.househomey.sort; +import com.example.househomey.Item; import com.example.househomey.tags.Tag; import java.util.Comparator; +import java.util.Set; -public class TagComparator implements Comparator { +public class TagComparator implements Comparator { + /** + * Compares the first tag of each item + * @param item1 the first object to be compared. + * @param item2 the second object to be compared. + * @return 1 if o1 is o2, 0 if tied, -1 otherwise + */ @Override - public int compare(Tag tag1, Tag tag2) { - return tag1.getTagLabel().compareTo(tag2.getTagLabel()); + public int compare(Item item1, Item item2) { + Tag tag1 = getFirstTag(item1); + Tag tag2 = getFirstTag(item2); + + if (tag1 == null && tag2 == null) { + return 0; + } else if (tag1 == null) { + return 1; + } else if (tag2 == null) { + return -1; + } else { + return tag1.compareTo(tag2); + } + } + + private Tag getFirstTag(Item item) { + Set tags = item.getTags(); + if (tags.isEmpty()) { + return null; + } else { + return tags.iterator().next(); + } } } diff --git a/app/src/main/java/com/example/househomey/tags/ApplyTagFragment.java b/app/src/main/java/com/example/househomey/tags/ApplyTagFragment.java new file mode 100644 index 00000000..8991475a --- /dev/null +++ b/app/src/main/java/com/example/househomey/tags/ApplyTagFragment.java @@ -0,0 +1,100 @@ +package com.example.househomey.tags; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.core.os.BundleCompat; + +import com.example.househomey.Item; +import com.example.househomey.MainActivity; +import com.example.househomey.R; +import com.example.househomey.utils.FragmentUtils; +import com.google.android.material.chip.Chip; +import com.google.firebase.firestore.FieldValue; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.WriteBatch; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * DialogFragment that allows users to add new tags + * @author Matthew Neufeld + */ +public class ApplyTagFragment extends TagFragment implements Serializable { + private ArrayList selectedItems; + + /** + * Called to create the dialog, initializing UI components and setting up button listeners. + * + * @param savedInstanceState Bundle containing the saved state of the fragment. + * @return The created AlertDialog. + */ + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + View rootView = requireActivity().getLayoutInflater().inflate(R.layout.fragment_apply_tags, null); + this.tagRef = ((MainActivity) requireActivity()).getTagRef(); + selectedItems = BundleCompat.getParcelableArrayList(getArguments(), "itemList", Item.class); + + chipGroup = rootView.findViewById(R.id.chip_group_labels); + getTagCollection(); + + return new AlertDialog.Builder(getActivity()) + .setView(rootView) + .setTitle("Select Tags") + .setNeutralButton("Cancel", null) + .setNegativeButton("Manage", (d, w) -> { + ManageTagFragment manageTagFragment = new ManageTagFragment(); + + Bundle tagArgs = new Bundle(); + tagArgs.putParcelableArrayList("itemList", selectedItems); + manageTagFragment.setArguments(tagArgs); + manageTagFragment.show(requireActivity().getSupportFragmentManager(), "tagDialog"); + }) + .setPositiveButton("Apply", (dialog, which) -> applyItemsToTag(rootView, selectedItems)) + .create(); + } + + /** + * Creates a new chip for the new tag + * + * @param label Name of the tag for the new chip + */ + protected void makeTagChip(String label) { + FragmentUtils.makeChip(label, false, chipGroup, requireContext(), R.drawable.tag_chip, R.color.brown, R.color.brown, true); + } + + /** + * Save items to Firestore for the given tags. + * + * @param selectedItems Items to which the tags will be applied + */ + private void applyItemsToTag(View rootView, ArrayList selectedItems) { + ArrayList selectedTags = new ArrayList<>(); + for (int id : chipGroup.getCheckedChipIds()) { + Chip chip = rootView.findViewById(id); + selectedTags.add(chip.getText().toString()); + } + if (!selectedItems.isEmpty()) { + List idList = selectedItems.stream() + .map(Item::getId) + .collect(Collectors.toList()); + WriteBatch batch = FirebaseFirestore.getInstance().batch(); + for (String tag : selectedTags) { + batch.update(tagRef.document(tag), + "items", FieldValue.arrayUnion(idList.toArray())); + } + batch.commit() + .addOnSuccessListener((result) -> Log.i("Firestore", "Items successfully applied to tag")) + .addOnFailureListener((error) -> Log.e("Firestore", "Failed to apply items to tag.", error)); + } + } + +} diff --git a/app/src/main/java/com/example/househomey/tags/ManageTagFragment.java b/app/src/main/java/com/example/househomey/tags/ManageTagFragment.java new file mode 100644 index 00000000..a6507d89 --- /dev/null +++ b/app/src/main/java/com/example/househomey/tags/ManageTagFragment.java @@ -0,0 +1,102 @@ +package com.example.househomey.tags; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.core.os.BundleCompat; + +import com.example.househomey.Item; +import com.example.househomey.MainActivity; +import com.example.househomey.R; +import com.example.househomey.utils.FragmentUtils; +import com.google.android.material.chip.Chip; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class ManageTagFragment extends TagFragment { + private EditText tagEditText; + private Button addTagButton; + + /** + * Called to create the dialog, initializing UI components and setting up button listeners. + * + * @param savedInstanceState Bundle containing the saved state of the fragment. + * @return The created AlertDialog. + */ + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + View rootView = requireActivity().getLayoutInflater().inflate(R.layout.fragment_manage_tags, null); + this.tagRef = ((MainActivity) requireActivity()).getTagRef(); + final ArrayList selectedItems = BundleCompat.getParcelableArrayList(getArguments(), "itemList", Item.class); + + // Initialize UI components + chipGroup = rootView.findViewById(R.id.chip_group_labels); + tagEditText = rootView.findViewById(R.id.tag_edit_text); + addTagButton = rootView.findViewById(R.id.add_tag_button); + + // Set up button click listener to add tags + addTagButton.setOnClickListener(v -> { + String tagLabel = tagEditText.getText().toString().trim(); + if (!tagLabel.isEmpty()) + addTag(tagLabel); + }); + + getTagCollection(); + + // Build the dialog window + AlertDialog dialog = new AlertDialog.Builder(getActivity()) + .setView(rootView) + .setTitle("Manage Tags") + .setPositiveButton("Done", (d, which) -> { + ApplyTagFragment applyTagFragment = new ApplyTagFragment(); + Bundle tagArgs = new Bundle(); + tagArgs.putParcelableArrayList("itemList", selectedItems); + applyTagFragment.setArguments(tagArgs); + applyTagFragment.show(requireActivity().getSupportFragmentManager(),"tagDialog"); + }) + .create(); + dialog.setCanceledOnTouchOutside(false); + return dialog; + } + + /** + * Creates a new chip for the new tag that is deletable + * @param label Name of the tag for the new chip + */ + protected void makeTagChip(String label) { + final Chip chip = FragmentUtils.makeChip(label, true, chipGroup, requireContext(), R.drawable.tag_chip, R.color.brown, R.color.brown); + chip.setOnCloseIconClickListener(v -> + tagRef.document(label).delete().addOnSuccessListener(result -> { + chipGroup.removeView(chip); + tagList.removeIf(tag -> tag.getTagLabel().equals(chip.getText().toString())); + }) + ); + } + + /** + * Adds a new tag to the ChipGroup and the tag list when addTagButton is clicked. + * @param tagLabel the label that will go on the tag + */ + private void addTag(String tagLabel) { + if (!tagList.stream() + .map(Tag::getTagLabel) + .collect(Collectors.toList()).contains(tagLabel)) { + Tag tag = new Tag(tagLabel, new HashMap<>()); + tagList.add(tag); + makeTagChip(tagLabel); + Map data = new HashMap<>(); + data.put("items", new ArrayList<>()); + tagRef.document(tagLabel).set(data); + } + tagEditText.getText().clear(); + } +} diff --git a/app/src/main/java/com/example/househomey/tags/Tag.java b/app/src/main/java/com/example/househomey/tags/Tag.java index e80231eb..c9e2f315 100644 --- a/app/src/main/java/com/example/househomey/tags/Tag.java +++ b/app/src/main/java/com/example/househomey/tags/Tag.java @@ -20,7 +20,7 @@ * This class represents a tag object * @author Matthew Neufeld */ -public class Tag implements Parcelable { +public class Tag implements Parcelable, Comparable { private String tagLabel; private ArrayList itemIds = new ArrayList<>(); @@ -36,6 +36,16 @@ public Tag(String tagLabel, @NonNull Map data) { } } + /** + * Sorts by alphabetical tag + * @param o the object to be compared. + * @return The compared objects + */ + @Override + public int compareTo(Tag o) { + return this.getTagLabel().compareTo(o.getTagLabel()); + } + /** * Getter for the tag label * @return tag label for the tag @@ -44,6 +54,10 @@ public String getTagLabel() { return tagLabel; } + /** + * Getter for item ids + * @return list of item ids with this tag + */ public ArrayList getItemIds() { return itemIds; } diff --git a/app/src/main/java/com/example/househomey/tags/TagFragment.java b/app/src/main/java/com/example/househomey/tags/TagFragment.java index f8d5f97c..4d27d1b1 100644 --- a/app/src/main/java/com/example/househomey/tags/TagFragment.java +++ b/app/src/main/java/com/example/househomey/tags/TagFragment.java @@ -1,157 +1,28 @@ package com.example.househomey.tags; -import android.app.AlertDialog; -import android.app.Dialog; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; - -import androidx.core.os.BundleCompat; import androidx.fragment.app.DialogFragment; -import com.example.househomey.Item; -import com.example.househomey.MainActivity; -import com.example.househomey.R; -import com.example.househomey.utils.FragmentUtils; import com.google.android.material.chip.Chip; import com.google.android.material.chip.ChipGroup; import com.google.firebase.firestore.CollectionReference; -import com.google.firebase.firestore.FieldValue; -import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.QueryDocumentSnapshot; -import com.google.firebase.firestore.WriteBatch; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; -/** - * DialogFragment that allows users to add new tags - * @author Matthew Neufeld - */ -public class TagFragment extends DialogFragment { - private ChipGroup chipGroup; - private EditText tagEditText; - private Button addTagButton; - private Chip chip; - private List tagList = new ArrayList<>(); - private ArrayList selectedItems; - private CollectionReference tagRef; - - /** - * Called to create the dialog, initializing UI components and setting up button listeners. - * - * @param savedInstanceState Bundle containing the saved state of the fragment. - * @return The created AlertDialog. - */ - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - this.tagRef = ((MainActivity) requireActivity()).getTagRef(); - - Bundle args = getArguments(); - selectedItems = BundleCompat.getParcelableArrayList(args, "itemList", Item.class); - - // Initialize AlertDialog builder - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - - // Inflate the layout for this fragment - LayoutInflater inflater = requireActivity().getLayoutInflater(); - View rootView = inflater.inflate(R.layout.fragment_tags, null); - - // Initialize UI components - chipGroup = rootView.findViewById(R.id.chip_group_labels); - tagEditText = rootView.findViewById(R.id.tag_edit_text); - addTagButton = rootView.findViewById(R.id.add_tag_button); - - // Set up button click listener to add tags - addTagButton.setOnClickListener(v -> { - String tagLabel = tagEditText.getText().toString().trim(); - if (!tagLabel.isEmpty()) - addTag(tagLabel); - }); - - getTagCollection(); - - return builder - .setView(rootView) - .setTitle("Tags") - .setNeutralButton("Cancel", null) - .setPositiveButton("Apply Tags", (dialog, which) -> applyItemsToTag(rootView, selectedItems)) - .create(); - } - - /** - * Adds a new tag to the ChipGroup and the tag list when addTagButton is clicked. - * @param tagLabel the label that will go on the tag - */ - private void addTag(String tagLabel) { - if (!tagList.stream() - .map(Tag::getTagLabel) - .collect(Collectors.toList()).contains(tagLabel)) { - Tag tag = new Tag(tagLabel, new HashMap<>()); - tagList.add(tag); - makeTagChip(tagLabel); - Map data = new HashMap<>(); - data.put("items", new ArrayList<>()); - tagRef.document(tagLabel).set(data); - } - tagEditText.getText().clear(); - } +public abstract class TagFragment extends DialogFragment { + protected CollectionReference tagRef; + protected List tagList = new ArrayList<>(); + protected ChipGroup chipGroup; - /** - * Creates a new chip for the new tag with a delete option - * @param label Name of the tag for the new chip - */ - private void makeTagChip(String label) { - chip = FragmentUtils.makeChip(label, true, chipGroup, requireContext(), R.drawable.tag_chip, R.color.brown, R.color.brown, true); - final Chip thisChip = chip; - chip.setOnCloseIconClickListener(v -> - tagRef.document(label).delete().addOnSuccessListener(result -> { - chipGroup.removeView(thisChip); - tagList.removeIf(tag -> tag.getTagLabel().equals(thisChip.getText().toString())); - }) - ); - } + abstract void makeTagChip(String label); - /** - * Save items to Firestore for the given tags. - * @param selectedItems Items to which the tags will be applied - */ - private void applyItemsToTag(View rootView, ArrayList selectedItems) { - ArrayList selectedTags = new ArrayList<>(); - for (int id: chipGroup.getCheckedChipIds()) { - Chip chip = rootView.findViewById(id); - selectedTags.add(chip.getText().toString()); - } - if (!selectedItems.isEmpty()) { - List idList = selectedItems.stream() - .map(Item::getId) - .collect(Collectors.toList()); - WriteBatch batch = FirebaseFirestore.getInstance().batch(); - for (String tag : selectedTags) { - batch.update(tagRef.document(tag), - "items", FieldValue.arrayUnion(idList.toArray())); - } - batch.commit() - .addOnSuccessListener((result) -> { - Log.i("Firestore", "Items successfully applied to tag"); - }) - .addOnFailureListener((error) -> { - Log.e("Firestore", "Failed to apply items to tag.", error); - }); - } - } /** * Gets a snapshot of the tags collection in Firestore */ - private void getTagCollection() { + protected void getTagCollection() { tagRef.get().addOnSuccessListener(queryDocumentSnapshots -> { for (QueryDocumentSnapshot document : queryDocumentSnapshots) { Tag tag = new Tag(document.getId(), document.getData()); diff --git a/app/src/main/res/layout/base_toolbar.xml b/app/src/main/res/layout/base_toolbar.xml index 28bed5cb..b4ee78ff 100644 --- a/app/src/main/res/layout/base_toolbar.xml +++ b/app/src/main/res/layout/base_toolbar.xml @@ -11,7 +11,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/select" - android:textSize="20sp" + android:textSize="16sp" android:padding="0dp" android:textStyle="bold" /> diff --git a/app/src/main/res/layout/fragment_apply_tags.xml b/app/src/main/res/layout/fragment_apply_tags.xml new file mode 100644 index 00000000..f905d237 --- /dev/null +++ b/app/src/main/res/layout/fragment_apply_tags.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_filter_by_keywords.xml b/app/src/main/res/layout/fragment_filter_by_keywords.xml index cf857b94..13c9fef1 100644 --- a/app/src/main/res/layout/fragment_filter_by_keywords.xml +++ b/app/src/main/res/layout/fragment_filter_by_keywords.xml @@ -38,12 +38,15 @@ app:iconTint="@color/black" /> - - - + android:layout_height="wrap_content"> + + + diff --git a/app/src/main/res/layout/fragment_tags.xml b/app/src/main/res/layout/fragment_manage_tags.xml similarity index 75% rename from app/src/main/res/layout/fragment_tags.xml rename to app/src/main/res/layout/fragment_manage_tags.xml index 9c95aa0e..dea1fcdb 100644 --- a/app/src/main/res/layout/fragment_tags.xml +++ b/app/src/main/res/layout/fragment_manage_tags.xml @@ -9,20 +9,22 @@ + + android:inputType="text" /> - - + android:layout_height="wrap_content"> + + + diff --git a/app/src/main/res/layout/fragment_view_item.xml b/app/src/main/res/layout/fragment_view_item.xml index 58b7c638..0cd069fd 100644 --- a/app/src/main/res/layout/fragment_view_item.xml +++ b/app/src/main/res/layout/fragment_view_item.xml @@ -1,6 +1,5 @@ - + + + android:layout_height="match_parent"> + - + android:adjustViewBounds="true" + android:scaleType="fitXY" /> + + android:gravity="center_vertical" + android:orientation="vertical" + android:paddingHorizontal="12dp"> + android:textColor="@color/subTitle" + android:textSize="18sp" /> @@ -92,30 +95,29 @@ android:id="@+id/view_photo_grid" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingHorizontal="12dp" android:layout_gravity="center" + android:paddingHorizontal="12dp" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:spanCount="4" /> + android:layout_height="wrap_content" /> + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingHorizontal="12dp"> @@ -125,25 +127,25 @@ + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingHorizontal="12dp"> + android:textColor="@color/subTitle" + android:textSize="18sp" /> @@ -151,25 +153,25 @@ + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingHorizontal="12dp"> + android:textColor="@color/subTitle" + android:textSize="18sp" /> @@ -177,25 +179,25 @@ + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingHorizontal="12dp"> + android:textColor="@color/subTitle" + android:textSize="18sp" /> @@ -203,25 +205,25 @@ + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingHorizontal="12dp"> + android:textColor="@color/subTitle" + android:textSize="18sp" /> @@ -229,94 +231,106 @@ + + + + + + - - - + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingHorizontal="12dp"> - - - - + + - - - - + + + + + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingHorizontal="12dp"> + + android:layout_marginTop="16dp" + android:orientation="horizontal"> + android:layout_weight="1" />