diff --git a/Listify/app/src/main/java/com/example/listify/SearchResults.java b/Listify/app/src/main/java/com/example/listify/SearchResults.java index 4058a5e..e87fd48 100644 --- a/Listify/app/src/main/java/com/example/listify/SearchResults.java +++ b/Listify/app/src/main/java/com/example/listify/SearchResults.java @@ -1,5 +1,4 @@ package com.example.listify; -import android.media.Image; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; @@ -11,18 +10,29 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.SearchView; import android.widget.Toast; - import com.example.listify.adapter.SearchResultsListAdapter; import com.example.listify.model.Product; - import java.util.ArrayList; +import java.util.Comparator; import java.util.List; -public class SearchResults extends AppCompatActivity { +public class SearchResults extends AppCompatActivity implements SortDialogFragment.OnSortingListener { private ListView listView; private SearchResultsListAdapter searchResultsListAdapter; - private List productList = new ArrayList<>(); + private List resultsProductList = new ArrayList<>(); + private List resultsProductListSorted = new ArrayList<>(); + private ArrayList stores = new ArrayList<>(); + private int storeSelection; + private int sortMode; + private boolean descending; + @Override + public void sendSort(int storeSelection, int sortMode, boolean descending) { + this.storeSelection = storeSelection; + this.sortMode = sortMode; + this.descending = descending; + sortResults(); + } @Override protected void onCreate(Bundle savedInstanceState) { @@ -62,12 +72,12 @@ public class SearchResults extends AppCompatActivity { }); listView = (ListView) findViewById(R.id.search_results_list); - searchResultsListAdapter = new SearchResultsListAdapter(this, productList); + searchResultsListAdapter = new SearchResultsListAdapter(this, resultsProductListSorted); listView.setAdapter(searchResultsListAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - Toast.makeText(SearchResults.this, productList.get(position).getItemName(), Toast.LENGTH_SHORT).show(); + Toast.makeText(SearchResults.this, resultsProductListSorted.get(position).getItemName(), Toast.LENGTH_SHORT).show(); } }); @@ -75,9 +85,7 @@ public class SearchResults extends AppCompatActivity { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - doSearch(query); - // TODO: Display the search results listview return false; } @@ -87,8 +95,28 @@ public class SearchResults extends AppCompatActivity { } }); + // Create a dialog for filtering and sorting search results + ImageButton sortButton = (ImageButton) findViewById(R.id.results_sort_button); + sortButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // Sort the store list + stores.sort(new Comparator() { + @Override + public int compare(String o1, String o2) { + return o1.compareTo(o2); + } + }); + SortDialogFragment sortDialog = new SortDialogFragment(storeSelection, stores, sortMode, descending); + sortDialog.show(getSupportFragmentManager(), "Sort"); + } + }); + } + + + // Override default phone back button to add animation @Override public void onBackPressed() { @@ -96,23 +124,109 @@ public class SearchResults extends AppCompatActivity { overridePendingTransition(R.anim.enter_from_right, R.anim.exit_from_right); } - // TODO: This function will handle the search operation with the database and return - // a listview to caller to be displayed - private ListView doSearch(String query) { - // Hardcode some search results... - Product a = new Product("Bottled Water", "0000", "Walmart", "0001", "0123456780", "Bro, it's water...", "Grocery", "$13.37", "9/24/2020", "1", "http://3.bp.blogspot.com/-MfroPPQVDKo/UyhUZWqGvkI/AAAAAAAAB-I/DGk622onsvc/s1600/lettuce-b-kool-cat-meme.jpg"); - Product b = new Product("Tin Foil", "0001", "Walmart", "0001", "0123456781", "Not aluminum foil", "Grocery", "$1.00", "9/24/2020", "1", "https://i.ytimg.com/vi/q9N1doYMxR0/maxresdefault.jpg"); - Product c = new Product("Lettuce", "0002", "Walmart", "0001", "0123456782", "It's still wet", "Grocery", "$0.60", "9/24/2020", "1", "https://www.cattitudedaily.com/wp-content/uploads/2019/12/white-cat-meme-feature.jpg"); - Product d = new Product("Video Game", "0003", "Walmart", "0001", "0123456783", "Fun Vidya Gaemz", "Electronics", "$60.00", "9/24/2020", "1", "https://i1.wp.com/bestlifeonline.com/wp-content/uploads/2018/06/cat-meme-67.jpg?resize=1024%2C1024&ssl=1"); - Product e = new Product("Mountain Dew", "0004", "Walmart", "0001", "0123456784", "Gamer fuel", "Grocery", "$5.87", "9/24/2020", "1", "https://memeguy.com/photos/images/gaming-cat-7680.png"); - Product f = new Product("Tire", "0005", "Walmart", "0001", "0123456785", "30 inch rims", "Automotive", "$146.97", "9/24/2020", "1", "http://cdn.sheknows.com/articles/2013/05/pet5.jpg"); - productList.add(a); - productList.add(b); - productList.add(c); - productList.add(d); - productList.add(e); - productList.add(f); + private void doSearch(String query) { + // TODO: Query Database + // TODO: Create a new Product Object for each result + // TODO: Add each result to productList - return null; + // Hardcode some search results... + for (int i = 0; i < 2; i++) { + Product a = new Product("Bottled Water", "0000", "Walmart", "0001", "0123456780", "Bro, it's water...", "Grocery", 13.37, "9/24/2020", "1", "http://3.bp.blogspot.com/-MfroPPQVDKo/UyhUZWqGvkI/AAAAAAAAB-I/DGk622onsvc/s1600/lettuce-b-kool-cat-meme.jpg"); + Product b = new Product("Tin Foil", "0001", "Walmart", "0001", "0123456781", "Not aluminum foil", "Grocery", 1.00, "9/24/2020", "1", "https://i.ytimg.com/vi/q9N1doYMxR0/maxresdefault.jpg"); + Product c = new Product("Lettuce", "0002", "Walmart", "0001", "0123456782", "Burger King foot lettuce", "Grocery", 0.60, "9/24/2020", "1", "https://www.cattitudedaily.com/wp-content/uploads/2019/12/white-cat-meme-feature.jpg"); + Product d = new Product("Video Game", "0003", "Walmart", "0001", "0123456783", "Fun Vidya Gaemz", "Electronics", 60.00, "9/24/2020", "1", "https://i1.wp.com/bestlifeonline.com/wp-content/uploads/2018/06/cat-meme-67.jpg?resize=1024%2C1024&ssl=1"); + Product e = new Product("Mountain Dew", "0004", "Walmart", "0001", "0123456784", "Gamer fuel", "Grocery", 5.87, "9/24/2020", "1", "https://memeguy.com/photos/images/gaming-cat-7680.png"); + Product f = new Product("Tire", "0005", "Walmart", "0001", "0123456785", "30 inch rims", "Automotive", 146.97, "9/24/2020", "1", "http://cdn.sheknows.com/articles/2013/05/pet5.jpg"); + resultsProductList.add(a); + resultsProductList.add(b); + resultsProductList.add(c); + resultsProductList.add(d); + resultsProductList.add(e); + resultsProductList.add(f); + } + + // 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(); + } + + // Sorts the search results + private void sortResults() { + // Sort Modes + // 0 default (no sorting) + // 1 itemName + // 2 price + // 3 chainName + // 4 upc + + // Sort based on mode + switch (this.sortMode) { + case 0: + resultsProductListSorted.clear(); + resultsProductListSorted.addAll(resultsProductList); + searchResultsListAdapter.notifyDataSetChanged(); + return; + case 1: + resultsProductListSorted.sort(new Comparator() { + @Override + public int compare(Product a, Product b) { + return a.getItemName().compareToIgnoreCase(b.getItemName()); + } + }); + break; + + // TODO: May need to change this depending on if price is stored as a string or a double + case 2: + resultsProductListSorted.sort(new Comparator() { + @Override + public int compare(Product a, Product b) { + if (b.getPrice() - a.getPrice() > 0) { + return 1; + } else if (b.getPrice() - a.getPrice() < 0) { + return -1; + } else { + return 0; + } + } + }); + break; + + case 3: + resultsProductListSorted.sort(new Comparator() { + @Override + public int compare(Product a, Product b) { + return a.getChainName().compareToIgnoreCase(b.getChainName()); + } + }); + break; + + case 4: + resultsProductListSorted.sort(new Comparator() { + @Override + public int compare(Product a, Product b) { + return a.getUpc().compareToIgnoreCase(b.getUpc()); + } + }); + break; + } + + if (this.descending) { + for (int i = 0; i < resultsProductListSorted.size() / 2; i++) { + Product temp = resultsProductListSorted.get(i); + resultsProductListSorted.set(i, resultsProductListSorted.get(resultsProductListSorted.size() - i - 1)); + resultsProductListSorted.set(resultsProductListSorted.size() - i - 1, temp); + } + } + + searchResultsListAdapter.notifyDataSetChanged(); } } \ No newline at end of file diff --git a/Listify/app/src/main/java/com/example/listify/SortDialogFragment.java b/Listify/app/src/main/java/com/example/listify/SortDialogFragment.java new file mode 100644 index 0000000..ac4713f --- /dev/null +++ b/Listify/app/src/main/java/com/example/listify/SortDialogFragment.java @@ -0,0 +1,152 @@ +package com.example.listify; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageButton; +import android.widget.Spinner; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import java.util.ArrayList; + + +public class SortDialogFragment extends DialogFragment { + + public interface OnSortingListener { + void sendSort(int storeSelection, int sortMode, boolean descending); + } + + public OnSortingListener onSortingListener; + + private int storeSelection; + private int sortMode; + private boolean descending; + private ArrayList stores; + + public SortDialogFragment(int storeSelection, ArrayList stores, int sortMode, boolean descending) { + this.storeSelection = storeSelection; + this.stores = stores; + this.sortMode = sortMode; + this.descending = descending; + } + + + @Override + public Dialog onCreateDialog(final Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + // Get the layout inflater + LayoutInflater inflater = requireActivity().getLayoutInflater(); + + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + View root = inflater.inflate(R.layout.dialog_sort, null); + builder.setView(root) + // Add action buttons + .setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + onSortingListener.sendSort(storeSelection, sortMode, descending); + } + }) + .setNegativeButton("cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + SortDialogFragment.this.getDialog().cancel(); + } + }); + + Spinner storeDropdown = (Spinner) root.findViewById(R.id.sort_store_dropdown); + String[] storeChoices = new String[stores.size() + 1]; + storeChoices[0] = "All"; + for (int i = 1; i < stores.size() + 1; i++) { + storeChoices[i] = stores.get(i - 1); + } + + // Create the store selection dropdown + ArrayAdapter storeAdapter = new ArrayAdapter<>(root.getContext(), android.R.layout.simple_spinner_dropdown_item, storeChoices); + storeDropdown.setAdapter(storeAdapter); + storeDropdown.setSelection(this.storeSelection); + storeDropdown.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + storeSelection = position; + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + // Change the sort arrow to be pointing up or down based on ascending or descending + final ImageButton sortDirectionButton = root.findViewById(R.id.sort_direction_button); + if (descending) { + sortDirectionButton.setImageResource(R.drawable.ic_baseline_arrow_downward_50); + } else { + sortDirectionButton.setImageResource(R.drawable.ic_baseline_arrow_upward_50); + } + + // Change array pointing direction whenever the user clicks the button + sortDirectionButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (descending) { + descending = false; + sortDirectionButton.setImageResource(R.drawable.ic_baseline_arrow_upward_50); + } else { + descending = true; + sortDirectionButton.setImageResource(R.drawable.ic_baseline_arrow_downward_50); + } + } + }); + + // Create the sort mode selection dropdown + Spinner sortDropdown = (Spinner) root.findViewById(R.id.sort_mode_dropdown); + String[] items = new String[] {"", "Name", "Price", "Store"}; + ArrayAdapter adapter = new ArrayAdapter<>(root.getContext(), android.R.layout.simple_spinner_dropdown_item, items); + sortDropdown.setAdapter(adapter); + sortDropdown.setSelection(this.sortMode); + sortDropdown.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + sortMode = position; + + // Update the sort direction button + if (position == 0) { + sortDirectionButton.setEnabled(false); + } else { + sortDirectionButton.setEnabled(true); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + // Disable the direction button if they have the default sorting mode selected + // Ascending and Descending are mostly irrelevant in the default sort mode + if (sortDropdown.getSelectedItemPosition() == 0) { + sortDirectionButton.setEnabled(false); + } + + return builder.create(); + } + + // Required to extend DialogFragment + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + try { + onSortingListener = (OnSortingListener) getActivity(); + } catch (ClassCastException e) { + Log.e("SortDialogFragment", "onAttach: ClassCastException: " + e.getMessage()); + } + } +} diff --git a/Listify/app/src/main/java/com/example/listify/adapter/SearchResultsListAdapter.java b/Listify/app/src/main/java/com/example/listify/adapter/SearchResultsListAdapter.java index b231906..61f9473 100644 --- a/Listify/app/src/main/java/com/example/listify/adapter/SearchResultsListAdapter.java +++ b/Listify/app/src/main/java/com/example/listify/adapter/SearchResultsListAdapter.java @@ -42,23 +42,24 @@ public class SearchResultsListAdapter extends BaseAdapter { @Override public View getView(int position, View convertView, ViewGroup parent) { - if(inflater == null){ + if (inflater == null) { inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - }if(convertView == null){ + } + if (convertView == null) { convertView = inflater.inflate(R.layout.search_list_item, null); } ImageView productImage = (ImageView) convertView.findViewById(R.id.item_image); TextView itemName = (TextView) convertView.findViewById(R.id.item_name); - TextView description = (TextView) convertView.findViewById(R.id.item_desc); TextView price = (TextView) convertView.findViewById(R.id.item_price); + TextView itemStore = (TextView) convertView.findViewById(R.id.item_store); Product product = productList.get(position); -// product.loadImageView(0, 0, productImage); + // TODO: If image url is broken, display @drawable/ic_baseline_broken_image_600.xml Glide.with(activity).load(product.getImageUrl()).into(productImage); itemName.setText(product.getItemName()); - description.setText(product.getDescription()); - price.setText(product.getPrice()); + price.setText(String.format("$%.2f", product.getPrice())); + itemStore.setText(product.getChainName()); return convertView; } diff --git a/Listify/app/src/main/java/com/example/listify/model/Product.java b/Listify/app/src/main/java/com/example/listify/model/Product.java index 63fb62e..db52386 100644 --- a/Listify/app/src/main/java/com/example/listify/model/Product.java +++ b/Listify/app/src/main/java/com/example/listify/model/Product.java @@ -1,17 +1,5 @@ package com.example.listify.model; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.os.AsyncTask; -import android.util.Log; -import android.widget.ImageView; - -import com.example.listify.R; - -import java.io.InputStream; - public class Product { private String itemName; private String itemId; @@ -20,7 +8,7 @@ public class Product { private String upc; private String description; private String department; - private String price; + private double price; private String retrievedDate; private String fetchCounts; private String imageUrl; @@ -28,7 +16,7 @@ public class Product { public Product() {} public Product(String itemName, String itemId, String chainName, String chainId, String upc, - String description, String department, String price, String retrievedDate, + String description, String department, double price, String retrievedDate, String fetchCounts, String imageUrl) { this.itemName = itemName; this.itemId = itemId; @@ -43,36 +31,6 @@ public class Product { this.imageUrl = imageUrl; } -// private class DownloadImageTask extends AsyncTask { -// ImageView imageView; -// -// public DownloadImageTask(ImageView imageView) { -// this.imageView = imageView; -// } -// -// protected Bitmap doInBackground(String... args) { -// String url = args[0]; -// Bitmap image = null; -// try { -// InputStream in = new java.net.URL(url).openStream(); -// image = BitmapFactory.decodeStream(in); -// } catch (Exception e) { -// Log.e("Error", e.getMessage()); -// e.printStackTrace(); -// } -// return image; -// } -// -// protected void onPostExecute(Bitmap result) { -// // Return the broken image icon as a bitmap if the url is invalid -// if (result == null) { -// imageView.setImageResource(R.drawable.ic_baseline_broken_image_600); -// } else { -// imageView.setImageBitmap(result); -// } -// } -// } - public String getItemName() { return itemName; } @@ -129,11 +87,11 @@ public class Product { this.department = department; } - public String getPrice() { + public double getPrice() { return price; } - public void setPrice(String price) { + public void setPrice(double price) { this.price = price; } @@ -160,9 +118,4 @@ public class Product { public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } - -// // TODO: Need to implement image resizing -// public void loadImageView(int height, int width, ImageView imageView) { -// new DownloadImageTask(imageView).execute(this.imageUrl); -// } } diff --git a/Listify/app/src/main/res/drawable/ic_baseline_arrow_downward_50.xml b/Listify/app/src/main/res/drawable/ic_baseline_arrow_downward_50.xml new file mode 100644 index 0000000..79f9241 --- /dev/null +++ b/Listify/app/src/main/res/drawable/ic_baseline_arrow_downward_50.xml @@ -0,0 +1,5 @@ + + + diff --git a/Listify/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml b/Listify/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml new file mode 100644 index 0000000..ce58346 --- /dev/null +++ b/Listify/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/Listify/app/src/main/res/drawable/ic_baseline_arrow_upward_50.xml b/Listify/app/src/main/res/drawable/ic_baseline_arrow_upward_50.xml new file mode 100644 index 0000000..a1ddf9b --- /dev/null +++ b/Listify/app/src/main/res/drawable/ic_baseline_arrow_upward_50.xml @@ -0,0 +1,5 @@ + + + diff --git a/Listify/app/src/main/res/drawable/ic_baseline_filter_list_28.xml b/Listify/app/src/main/res/drawable/ic_baseline_filter_list_28.xml new file mode 100644 index 0000000..4ecf051 --- /dev/null +++ b/Listify/app/src/main/res/drawable/ic_baseline_filter_list_28.xml @@ -0,0 +1,10 @@ + + + diff --git a/Listify/app/src/main/res/drawable/ic_baseline_search_28.xml b/Listify/app/src/main/res/drawable/ic_baseline_search_28.xml new file mode 100644 index 0000000..d14c485 --- /dev/null +++ b/Listify/app/src/main/res/drawable/ic_baseline_search_28.xml @@ -0,0 +1,10 @@ + + + diff --git a/Listify/app/src/main/res/drawable/ic_baseline_sort_28.xml b/Listify/app/src/main/res/drawable/ic_baseline_sort_28.xml new file mode 100644 index 0000000..60dff03 --- /dev/null +++ b/Listify/app/src/main/res/drawable/ic_baseline_sort_28.xml @@ -0,0 +1,5 @@ + + + diff --git a/Listify/app/src/main/res/layout/activity_search_results.xml b/Listify/app/src/main/res/layout/activity_search_results.xml index 457d2e1..2d8356e 100644 --- a/Listify/app/src/main/res/layout/activity_search_results.xml +++ b/Listify/app/src/main/res/layout/activity_search_results.xml @@ -36,10 +36,17 @@ > + - \ + \ No newline at end of file diff --git a/Listify/app/src/main/res/layout/app_bar_home.xml b/Listify/app/src/main/res/layout/app_bar_home.xml index 41992e1..285fb12 100644 --- a/Listify/app/src/main/res/layout/app_bar_home.xml +++ b/Listify/app/src/main/res/layout/app_bar_home.xml @@ -23,7 +23,7 @@ android:layout_gravity="end" android:layout_marginEnd="5dp" android:layout_height="wrap_content" - app:srcCompat="@android:drawable/ic_menu_search" + app:srcCompat="@drawable/ic_baseline_search_28" android:contentDescription="@string/search_button_desc" android:background="@null"/> diff --git a/Listify/app/src/main/res/layout/content_search_results.xml b/Listify/app/src/main/res/layout/content_search_results.xml index 5041fd3..cd08592 100644 --- a/Listify/app/src/main/res/layout/content_search_results.xml +++ b/Listify/app/src/main/res/layout/content_search_results.xml @@ -18,7 +18,5 @@ android:layout_height="wrap_content" android:divider="@color/list_divider" android:dividerHeight="1dp"/> - - \ No newline at end of file diff --git a/Listify/app/src/main/res/layout/dialog_sort.xml b/Listify/app/src/main/res/layout/dialog_sort.xml new file mode 100644 index 0000000..38e5002 --- /dev/null +++ b/Listify/app/src/main/res/layout/dialog_sort.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Listify/app/src/main/res/layout/search_list_item.xml b/Listify/app/src/main/res/layout/search_list_item.xml index 4bd41b7..469752d 100644 --- a/Listify/app/src/main/res/layout/search_list_item.xml +++ b/Listify/app/src/main/res/layout/search_list_item.xml @@ -9,64 +9,64 @@ + android:layout_alignParentStart="true"/> + + + + + + + + + + android:layout_toEndOf="@+id/item_image" + /> - - - - - - - - - - - - - - + android:text="" + android:textSize="12sp" + android:textStyle="bold" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true"/> \ No newline at end of file diff --git a/Listify/app/src/main/res/menu/main.xml b/Listify/app/src/main/res/menu/main.xml index 86282df..8730faa 100644 --- a/Listify/app/src/main/res/menu/main.xml +++ b/Listify/app/src/main/res/menu/main.xml @@ -6,9 +6,4 @@ android:orderInCategory="101" android:title="@string/action_settings" app:showAsAction="never" /> - diff --git a/Listify/app/src/main/res/values/strings.xml b/Listify/app/src/main/res/values/strings.xml index 82493fc..6299f7d 100644 --- a/Listify/app/src/main/res/values/strings.xml +++ b/Listify/app/src/main/res/values/strings.xml @@ -14,6 +14,9 @@ Listify Search Test SearchActivity + Product Image + Sort Icon + First Fragment Second Fragment @@ -25,4 +28,5 @@ Hello second fragment. Arg: %1$s Search Button SearchResults + Store selection \ No newline at end of file