├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── shop.png
│ │ │ │ ├── shop2.png
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── cart.png
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── menu
│ │ │ │ └── toolbar_menu.xml
│ │ │ ├── layout
│ │ │ │ ├── custom_toolbar1.xml
│ │ │ │ ├── quanity_seekbar.xml
│ │ │ │ ├── grocery_item_layout.xml
│ │ │ │ ├── activity_shop.xml
│ │ │ │ ├── activity_products.xml
│ │ │ │ ├── product_item_layout_child.xml
│ │ │ │ ├── product_item_layout.xml
│ │ │ │ ├── activity_finished.xml
│ │ │ │ ├── cart_item_layout.xml
│ │ │ │ ├── activity_checkout.xml
│ │ │ │ ├── activity_main_home_page.xml
│ │ │ │ ├── activity_login.xml
│ │ │ │ └── activity_cart.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── carsonskjerdal
│ │ │ │ └── app
│ │ │ │ └── groceryshop
│ │ │ │ ├── ProductsChild.java
│ │ │ │ ├── QuanitySeekBar.java
│ │ │ │ ├── Groceries.java
│ │ │ │ ├── ChildViewHolder.java
│ │ │ │ ├── ParentListItem.java
│ │ │ │ ├── BaseActivity.java
│ │ │ │ ├── Products.java
│ │ │ │ ├── CartItems.java
│ │ │ │ ├── CheckoutActivity.java
│ │ │ │ ├── MainHomePageActivity.java
│ │ │ │ ├── FinishedActivity.java
│ │ │ │ ├── ExpandableRecyclerAdapterHelper.java
│ │ │ │ ├── DatabaseManager.java
│ │ │ │ ├── ProductsExpandableAdapter.java
│ │ │ │ ├── ParentWrapper.java
│ │ │ │ ├── RecyclerItemClickListener.java
│ │ │ │ ├── GroceryAdapter.java
│ │ │ │ ├── ProductsParentViewHolder.java
│ │ │ │ ├── CartItemAdapter.java
│ │ │ │ ├── NotificationHandler.java
│ │ │ │ ├── CartActivity.java
│ │ │ │ ├── ProductsChildViewHolder.java
│ │ │ │ ├── ParentViewHolder.java
│ │ │ │ ├── GroceryActivity.java
│ │ │ │ ├── ProductsActivity.java
│ │ │ │ ├── DatabaseHelper.java
│ │ │ │ ├── LoginActivity.java
│ │ │ │ └── ExpandableRecyclerAdapter.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── carsonskjerdal
│ │ │ └── app
│ │ │ └── groceryshop
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── carsonskjerdal
│ │ └── app
│ │ └── groceryshop
│ │ ├── ExampleInstrumentedTest.java
│ │ └── MainHomePageActivityTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── _config.yml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── .idea
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
└── misc.xml
├── gradle.properties
├── README.md
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-time-machine
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/drawable/shop.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shop2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/drawable/shop2.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/cart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-anydpi-v26/cart.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carsonskjerdal/GroceryShop/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Nov 22 13:16:41 PST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ProductsChild.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 |
4 | /**
5 | * Created by Carson on 2017-11-28.
6 | *
7 | * Feel free to use code just give credit please :)
8 | */
9 |
10 | public class ProductsChild {
11 |
12 |
13 | private String mPrice;
14 |
15 | public ProductsChild(String price) {
16 | mPrice = price;
17 | }
18 |
19 |
20 | public String getPrice() {
21 | return mPrice;
22 | }
23 |
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/test/java/com/carsonskjerdal/app/groceryshop/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #c0c0c2
6 | #ff4081
7 |
8 | #89619b
9 | #695AA6
10 | #eeeeee
11 | #E1E0E0
12 | #D4D3D3
13 | #BCBBBD
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/toolbar_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
11 |
12 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/QuanitySeekBar.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.util.AttributeSet;
6 |
7 | /**
8 | * Created by Carson on 2017-12-06.
9 | *
10 | * Feel free to use code just give credit please :)
11 | */
12 |
13 | public class QuanitySeekBar extends android.support.v7.widget.AppCompatSeekBar {
14 |
15 | public QuanitySeekBar(Context context) {
16 | super(context);
17 | }
18 |
19 | public QuanitySeekBar (Context context, AttributeSet attrs, int defStyle) {
20 | super(context, attrs, defStyle);
21 | }
22 |
23 | public QuanitySeekBar (Context context, AttributeSet attrs) {
24 | super(context, attrs);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/custom_toolbar1.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/Groceries.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 |
4 | /**
5 | * Created by Carson on 2017-11-22.
6 | *
7 | * Feel free to use code just give credit please :)
8 | */
9 |
10 | public final class Groceries {
11 |
12 | private String name;
13 | private String image;
14 |
15 | public Groceries(String name, String image){
16 |
17 | this.name = name;
18 | this.image = image;
19 | }
20 |
21 | public void setName(String name) {
22 | this.name = name;
23 | }
24 |
25 | public String getName() {
26 | return name;
27 | }
28 |
29 | public void setImage(String image) {
30 | this.image = image;
31 | }
32 |
33 | public String getImage() {
34 | return image;
35 | }
36 |
37 |
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/quanity_seekbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ChildViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | /**
4 | * Created by Carson on 2017-12-05.
5 | *
6 | * Feel free to use code just give credit please :)
7 | */
8 |
9 |
10 | import android.support.v7.widget.RecyclerView;
11 | import android.view.View;
12 |
13 | /**
14 | * ViewHolder for a child list
15 | * item.
16 | *
17 | * The user should extend this class and implement as they wish for their
18 | * child list item.
19 | *
20 | * @author Ryan Brooks
21 | * @version 1.0
22 | * @since 5/27/2015
23 | */
24 | public class ChildViewHolder extends RecyclerView.ViewHolder {
25 |
26 | /**
27 | * Default constructor.
28 | *
29 | * @param itemView The {@link View} being hosted in this ViewHolder
30 | */
31 | public ChildViewHolder(View itemView) {
32 | super(itemView);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/carsonskjerdal/app/groceryshop/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.carsonskjerdal.app.groceryshop", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ParentListItem.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | /**
4 | * Created by Carson on 2017-12-05.
5 | *
6 | * Feel free to use code just give credit please :)
7 | */
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * Interface for implementing required methods in a parent list item.
13 | */
14 | public interface ParentListItem {
15 |
16 | /**
17 | * Getter for the list of this parent list item's child list items.
18 | *
19 | * If list is empty, the parent list item has no children.
20 | *
21 | * @return A {@link List} of the children of this {@link ParentListItem}
22 | */
23 | List> getChildItemList();
24 |
25 | /**
26 | * Getter used to determine if this {@link ParentListItem}'s
27 | * {@link android.view.View} should show up initially as expanded.
28 | *
29 | * @return true if expanded, false if not
30 | */
31 | boolean isInitiallyExpanded();
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | /**
7 | * Created by Carson on 2017-11-22.
8 | *
9 | * Feel free to use code just give credit please :)
10 | */
11 |
12 | public abstract class BaseActivity extends AppCompatActivity {
13 | @Override
14 | public void onCreate(Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | }
17 |
18 | }
19 |
20 | //TODO Master todo List
21 | //Pictures for images - working
22 | //Quanity passes over to Cart
23 | //Cart quantity data
24 | //Pick if cart details are correct and change the options
25 | //set up a logging in system
26 | //rotation
27 | //cancel item in order
28 | //change quanity
29 | //perhaps an information screen?
30 | //fix up finish screen to look nicer
31 | // (wierd line must be the image itself)
32 | //
33 | //extras
34 | //saving order history
35 | //delivery tracking
36 | //setting with features
37 | //
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/grocery_item_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
17 |
18 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GroceryShop
2 |
3 | *Notice: I built this years ago to test out some stuff. Its honestly garbage. Take it for kicks but this will probably cause you more headaches then you need. The code is outdated so best of luck*
4 |
5 | A grocery buying app allowing consumers to purchase and order groceries before you get to the store.
6 |
7 | ## Getting Started
8 |
9 | Clone the respository to your local machine
10 |
11 | $ git://github.com/towcar/GroceryShop.git
12 |
13 | ## Built With
14 |
15 | * [Braintree Payments](https://developers.braintreepayments.com/) - Card Form for Credit Card Data
16 | * [Wrdlbrnft](https://github.com/Wrdlbrnft/SortedListAdapter) - Recycler.View Adapter to Simplify Many Processes
17 |
18 | ## Versioning
19 |
20 | min SdkVersion 21 : targetSdkVersion 27
21 |
22 | ## Authors
23 |
24 | * **Carson Skjerdal** - *Initial work* - [Towcar](https://github.com/towcar)
25 |
26 | ## License
27 |
28 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
29 |
30 | ## Acknowledgments
31 |
32 | * Hat tip to many github projects for inspiration and stacked overflow for the endless source of knowledge
33 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/carsonskjerdal/app/groceryshop/MainHomePageActivityTest.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.support.test.espresso.Espresso;
4 | import android.support.test.rule.ActivityTestRule;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Rule;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static android.support.test.espresso.assertion.ViewAssertions.matches;
12 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
13 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
14 |
15 | /**
16 | * Created by Carson on 2018-01-13.
17 | *
18 | * Feel free to use code just give credit please :)
19 | */
20 |
21 | @RunWith(AndroidJUnit4.class)
22 | public class MainHomePageActivityTest {
23 |
24 | @Rule
25 | public ActivityTestRule activityTestRule = new ActivityTestRule<>(MainHomePageActivity.class);
26 |
27 | @Test
28 | public void TestItems(){
29 |
30 | //check shopButton is visible
31 | Espresso.onView(withId(R.id.shop_button)).check(matches((isDisplayed())));
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/Products.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 |
4 |
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Created by Carson on 2017-11-22.
10 | *
11 | * Feel free to use code just give credit please :)
12 | */
13 |
14 | public final class Products implements ParentListItem {
15 |
16 | private String name;
17 | private String image;
18 | private List mChildrenList;
19 |
20 | public Products(String name, String image, List productsChildren){
21 |
22 | this.name = name;
23 | this.image = image;
24 | mChildrenList = productsChildren;
25 | }
26 |
27 | public void setName(String name) {
28 | this.name = name;
29 | }
30 |
31 | public String getName() {
32 | return name;
33 | }
34 |
35 | public void setImage(String image) {
36 | this.image = image;
37 | }
38 |
39 | public String getImage() {
40 | return image;
41 | }
42 |
43 |
44 | @Override
45 | public List> getChildItemList() {
46 | return mChildrenList;
47 | }
48 |
49 | @Override
50 | public boolean isInitiallyExpanded() {
51 | return false;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_shop.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
18 |
19 |
20 |
28 |
29 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_products.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/product_item_layout_child.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
15 |
16 |
23 |
24 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 |
5 | compileSdkVersion 26
6 | defaultConfig {
7 | applicationId "com.carsonskjerdal.app.groceryshop"
8 | minSdkVersion 21
9 | targetSdkVersion 26
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 |
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 |
21 | }
22 |
23 | dataBinding {
24 | enabled = true
25 | }
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(dir: 'libs', include: ['*.jar'])
30 | implementation 'com.android.support:appcompat-v7:26.1.0'
31 | implementation 'com.android.support:design:26.1.0'
32 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
33 | compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.1'
34 | compile 'com.android.support:recyclerview-v7:26.1.0'
35 | compile 'com.braintreepayments:card-form:3.1.1'
36 | testImplementation 'junit:junit:4.12'
37 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
38 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/CartItems.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 |
4 | import android.util.Log;
5 |
6 | /**
7 | * Created by Carson on 2017-11-22.
8 | *
9 | * Feel free to use code just give credit please :)
10 | */
11 |
12 | public final class CartItems {
13 |
14 | private String name;
15 | private Integer image;
16 | private String price;
17 | private String quantity;
18 |
19 | public CartItems(String name, Integer image, String price, String quantity){
20 |
21 | this.name = name;
22 | this.image = image;
23 | this.price = price;
24 | this.quantity = quantity;
25 |
26 | }
27 |
28 | public void setName(String name) {
29 | this.name = name;
30 | }
31 |
32 | public String getName() {
33 | return name;
34 | }
35 |
36 | public void setImage(Integer image) {
37 | this.image = image;
38 | }
39 |
40 | public Integer getImage() {
41 | return image;
42 | }
43 |
44 |
45 | public void setPrice(String price) {
46 | this.price = price;
47 | }
48 |
49 | public String getPrice() {
50 | return price;
51 | }
52 |
53 | public void setQuantity(String quantity) {
54 | this.quantity = quantity;
55 | }
56 |
57 | public String getQuantity() {
58 | return quantity;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/product_item_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
17 |
18 |
28 |
29 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_finished.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
28 |
29 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | GroceryShop
3 |
4 |
5 | Email
6 | Password (optional)
7 | Sign in or register
8 | Sign in
9 | This email address is invalid
10 | This password is too short
11 | This password is incorrect
12 | This field is required
13 | "Contacts permissions are needed for providing email
14 | completions."
15 |
16 | TODO
17 | Shop Activity
18 | name
19 | Products Activity
20 | Quantity: 0
21 | Checkout
22 | Order Total:
23 | Your Order
24 | Submit
25 | Grocery Shop
26 | Add To Cart
27 | Please Enter Your Credit Card Details
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/CheckoutActivity.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Intent;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.widget.Button;
8 |
9 | import com.braintreepayments.cardform.view.CardForm;
10 |
11 | public class CheckoutActivity extends AppCompatActivity {
12 |
13 | Button buttonSubmit;
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_checkout);
19 |
20 | CardForm cardForm = findViewById(R.id.card_form);
21 | cardForm.cardRequired(true)
22 | .expirationRequired(true)
23 | .cvvRequired(true)
24 | .postalCodeRequired(true)
25 | .mobileNumberRequired(true)
26 | .mobileNumberExplanation("SMS is required on this number")
27 | .actionLabel("Purchase")
28 | .setup(this);
29 |
30 | buttonSubmit = findViewById(R.id.buttonFinish);
31 |
32 | buttonSubmit.setOnClickListener(new View.OnClickListener() {
33 | @Override
34 | public void onClick(View view) {
35 | //do purchase work..
36 |
37 | //launch finished activity
38 | Intent i = new Intent(CheckoutActivity.this, FinishedActivity.class);
39 |
40 | startActivity(i);
41 | }
42 | });
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/MainHomePageActivity.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.view.KeyEvent;
6 | import android.view.View;
7 | import android.widget.Button;
8 |
9 | public class MainHomePageActivity extends BaseActivity {
10 |
11 | //Ui References
12 | Button shopButton;
13 |
14 | @Override
15 | public void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_main_home_page);
18 |
19 | //assign buttons
20 | shopButton = findViewById(R.id.shop_button);
21 | shopButton.setOnClickListener(onClickListener);
22 |
23 |
24 | }
25 |
26 | //listens for button clicks to launch activities
27 | private View.OnClickListener onClickListener = new View.OnClickListener() {
28 |
29 | @Override
30 | public void onClick(final View v) {
31 | switch(v.getId()){
32 | case R.id.shop_button:
33 | //Launch Shop Activity
34 | Intent i = new Intent(MainHomePageActivity.this, GroceryActivity.class);
35 | startActivity(i);
36 |
37 | break;
38 |
39 | }
40 |
41 | }
42 | };
43 |
44 | @Override
45 | public boolean onKeyDown(int keyCode, KeyEvent event)
46 | {
47 | if ((keyCode == KeyEvent.KEYCODE_BACK))
48 | {
49 | finish();
50 | }
51 | return super.onKeyDown(keyCode, event);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/FinishedActivity.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Intent;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.os.Bundle;
6 | import android.view.MotionEvent;
7 | import android.view.View;
8 | import android.widget.Button;
9 |
10 | public class FinishedActivity extends AppCompatActivity {
11 |
12 | NotificationHandler nHandler;
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_finished);
18 |
19 | nHandler = NotificationHandler.getInstance(this);
20 | nHandler.createSimpleNotification(this);
21 |
22 | Button buttonFinish = findViewById(R.id.buttonFinish);
23 |
24 | buttonFinish.setOnClickListener(new View.OnClickListener() {
25 | @Override
26 | public void onClick(View view) {
27 | Intent intent = new Intent(getApplicationContext(), MainHomePageActivity.class);
28 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
29 | startActivity(intent);
30 | }
31 | });
32 | }
33 |
34 |
35 | @Override
36 | public boolean onTouchEvent(MotionEvent event) {
37 | //click event to let the user to click anywhere to move on, there is nothing else to click.
38 | //might remove since we have a button click, still neat to know though.
39 | Intent intent = new Intent(getApplicationContext(), MainHomePageActivity.class);
40 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
41 | startActivity(intent);
42 | return true;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/cart_item_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
17 |
18 |
28 |
29 |
39 |
40 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ExpandableRecyclerAdapterHelper.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | /**
4 | * Created by Carson on 2017-12-05.
5 | *
6 | * Feel free to use code just give credit please :)
7 | */
8 |
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 | /**
13 | * Helper for {@link ExpandableRecyclerAdapter}.
14 | *
15 | * Created by Ryan Brooks on 6/11/15.
16 | */
17 | public class ExpandableRecyclerAdapterHelper {
18 |
19 | /**
20 | * Generates a full list of all {@link ParentListItem} objects and their
21 | * children, in order.
22 | *
23 | * @param parentItemList A list of the {@code ParentListItem} objects from
24 | * the {@link ExpandableRecyclerAdapter}
25 | * @return A list of all {@code ParentListItem} objects and their children, expanded
26 | */
27 | public static List generateParentChildItemList(List extends ParentListItem> parentItemList) {
28 | List parentWrapperList = new ArrayList<>();
29 | ParentListItem parentListItem;
30 | ParentWrapper parentWrapper;
31 |
32 | int parentListItemCount = parentItemList.size();
33 | for (int i = 0; i < parentListItemCount; i++) {
34 | parentListItem = parentItemList.get(i);
35 | parentWrapper = new ParentWrapper(parentListItem);
36 | parentWrapperList.add(parentWrapper);
37 |
38 | if (parentWrapper.isInitiallyExpanded()) {
39 | parentWrapper.setExpanded(true);
40 |
41 | int childListItemCount = parentWrapper.getChildItemList().size();
42 | for (int j = 0; j < childListItemCount; j++) {
43 | parentWrapperList.add(parentWrapper.getChildItemList().get(j));
44 | }
45 | }
46 | }
47 |
48 | return parentWrapperList;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/DatabaseManager.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Context;
4 | import android.database.Cursor;
5 | import android.database.sqlite.SQLiteDatabase;
6 |
7 | /**
8 | * Created by Carson on 2017-12-13.
9 | *
10 | * Feel free to use code just give credit please :)
11 | *
12 | * Singleton that controls access to the SQLiteDatabase instance
13 | * for this application.
14 | */
15 |
16 | public class DatabaseManager {
17 | private Integer mOpenCounter = 0;
18 |
19 | private static DatabaseManager sInstance;
20 | private DatabaseHelper myDbHelper;
21 | private SQLiteDatabase mDatabase;
22 |
23 | public DatabaseManager(Context context) {
24 | myDbHelper = new DatabaseHelper(context);
25 | }
26 |
27 | public static synchronized DatabaseManager getInstance(Context context) {
28 | // Use the application context, which will ensure that you
29 | // don't accidentally leak an Activity's context.
30 | // See this article for more information: http://bit.ly/6LRzfx
31 | if (sInstance == null) {
32 | sInstance = new DatabaseManager(context.getApplicationContext());
33 | }
34 | return sInstance;
35 | }
36 |
37 | public synchronized SQLiteDatabase openDatabase() {
38 | mOpenCounter+=1;
39 | if(mOpenCounter == 1) {
40 | // Opening new database
41 | mDatabase = myDbHelper.getWritableDatabase();
42 | }
43 | return mDatabase;
44 | }
45 |
46 | public synchronized void closeDatabase() {
47 | mOpenCounter-=1;
48 | if(mOpenCounter == 0) {
49 | // Closing database
50 | mDatabase.close();
51 |
52 | }
53 | }
54 |
55 | public Cursor queryAllItems(String table) {
56 | //Implements the query
57 | SQLiteDatabase db = myDbHelper.getReadableDatabase();
58 | return db.rawQuery("select * from " + table, null);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ProductsExpandableAdapter.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 |
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * Created by Carson on 2017-11-28.
14 | *
15 | * Feel free to use code just give credit please :)
16 | */
17 |
18 | public class ProductsExpandableAdapter extends ExpandableRecyclerAdapter {
19 |
20 | LayoutInflater mInflater;
21 |
22 | public ProductsExpandableAdapter(Context context, List parentItemList) {
23 | super(parentItemList);
24 |
25 | mInflater = LayoutInflater.from(context);
26 | }
27 |
28 | //creates the main layout in the recycler view.
29 | @Override
30 | public ProductsParentViewHolder onCreateParentViewHolder(ViewGroup viewGroup) {
31 | View view = mInflater.inflate(R.layout.product_item_layout, viewGroup, false);
32 | return new ProductsParentViewHolder(view);
33 | }
34 |
35 | //creates the child layout
36 | @Override
37 | public ProductsChildViewHolder onCreateChildViewHolder(ViewGroup viewGroup) {
38 | View view = mInflater.inflate(R.layout.product_item_layout_child, viewGroup, false);
39 | return new ProductsChildViewHolder(view);
40 | }
41 |
42 |
43 | //binds daya with the main parent layout
44 | @Override
45 | public void onBindParentViewHolder(ProductsParentViewHolder productViewHolder, int position, ParentListItem parentListItem) {
46 | Products products = (Products) parentListItem;
47 | productViewHolder.bind(products);
48 | }
49 |
50 | //binds data with the child layout
51 | @Override
52 | public void onBindChildViewHolder(ProductsChildViewHolder productsChildViewHolder, int i, Object childObject) {
53 | ProductsChild productsChild = (ProductsChild) childObject;
54 | productsChildViewHolder.mDateText.setText(productsChild.getPrice());
55 |
56 | }
57 |
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ParentWrapper.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | /**
4 | * Created by Carson on 2017-12-05.
5 | *
6 | * Feel free to use code just give credit please :)
7 | */
8 |
9 |
10 | import java.util.List;
11 |
12 | /**
13 | * Wrapper used to link expanded state with a {@link ParentListItem}.
14 | *
15 | * @author Ryan Brooks
16 | * @version 1.0
17 | * @since 6/11/15
18 | */
19 | public class ParentWrapper {
20 |
21 | private boolean mExpanded;
22 | private ParentListItem mParentListItem;
23 |
24 | /**
25 | * Default constructor.
26 | *
27 | * @param parentListItem The {@link ParentListItem} to wrap
28 | */
29 | public ParentWrapper(ParentListItem parentListItem) {
30 | mParentListItem = parentListItem;
31 | mExpanded = false;
32 | }
33 |
34 | /**
35 | * Gets the {@link ParentListItem} being wrapped.
36 | *
37 | * @return The {@link ParentListItem} being wrapped
38 | */
39 | public ParentListItem getParentListItem() {
40 | return mParentListItem;
41 | }
42 |
43 | /**
44 | * Sets the {@link ParentListItem} to wrap.
45 | *
46 | * @param parentListItem The {@link ParentListItem} to wrap
47 | */
48 | public void setParentListItem(ParentListItem parentListItem) {
49 | mParentListItem = parentListItem;
50 | }
51 |
52 | /**
53 | * Gets the expanded state associated with the {@link ParentListItem}.
54 | *
55 | * @return true if expanded, false if not
56 | */
57 | public boolean isExpanded() {
58 | return mExpanded;
59 | }
60 |
61 | /**
62 | * Sets the expanded state associated with the {@link ParentListItem}.
63 | *
64 | * @param expanded true if expanded, false if not
65 | */
66 | public void setExpanded(boolean expanded) {
67 | mExpanded = expanded;
68 | }
69 |
70 | public boolean isInitiallyExpanded() {
71 | return mParentListItem.isInitiallyExpanded();
72 | }
73 |
74 | public List> getChildItemList() {
75 | return mParentListItem.getChildItemList();
76 | }
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/RecyclerItemClickListener.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | /**
4 | * Created by Carson on 2017-11-24.
5 | *
6 | * Feel free to use code just give credit please :)
7 | */
8 |
9 |
10 | import android.content.Context;
11 | import android.support.v7.widget.RecyclerView;
12 | import android.view.GestureDetector;
13 | import android.view.MotionEvent;
14 | import android.view.View;
15 |
16 |
17 | public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
18 | private OnItemClickListener mListener;
19 |
20 | public interface OnItemClickListener {
21 | void onItemClick(View view, int position);
22 |
23 | void onLongItemClick(View view, int position);
24 | }
25 |
26 | GestureDetector mGestureDetector;
27 |
28 | public RecyclerItemClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener) {
29 | mListener = listener;
30 | mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
31 | @Override
32 | public boolean onSingleTapUp(MotionEvent e) {
33 | return true;
34 | }
35 |
36 | @Override
37 | public void onLongPress(MotionEvent e) {
38 | View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
39 | if (child != null && mListener != null) {
40 | mListener.onLongItemClick(child, recyclerView.getChildAdapterPosition(child));
41 | }
42 | }
43 | });
44 | }
45 |
46 | @Override public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
47 | View childView = view.findChildViewUnder(e.getX(), e.getY());
48 | if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
49 | mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
50 | return true;
51 | }
52 | return false;
53 | }
54 |
55 | @Override public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) { }
56 |
57 | @Override
58 | public void onRequestDisallowInterceptTouchEvent (boolean disallowIntercept){}
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_checkout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
28 |
29 |
36 |
37 |
38 |
39 |
40 |
47 |
48 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/GroceryAdapter.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.res.Resources;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.util.Log;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 | import java.util.List;
13 |
14 | /**
15 | * Created by Carson on 2017-11-22.
16 | *
17 | * RecyclerView adapter extended with project-specific required methods.
18 | */
19 |
20 | public class GroceryAdapter extends RecyclerView.Adapter {
21 |
22 | private List groceryList;
23 | List displayedList;
24 |
25 | public GroceryAdapter(List list) {
26 | groceryList = list;
27 |
28 |
29 | }
30 |
31 | /* ViewHolder for each insect item */
32 | public class GroceryHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
33 |
34 | TextView name;
35 | ImageView image;
36 |
37 |
38 | GroceryHolder(View itemView) {
39 | super(itemView);
40 | name = itemView.findViewById(R.id.name);
41 | image = itemView.findViewById(R.id.image);
42 | }
43 |
44 |
45 |
46 | @Override
47 | public void onClick(View v) {
48 |
49 | }
50 | }
51 |
52 | @Override
53 | public GroceryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
54 | View itemView = LayoutInflater.from(parent.getContext())
55 | .inflate(R.layout.grocery_item_layout, parent, false);
56 |
57 |
58 | return new GroceryHolder(itemView);
59 | }
60 |
61 | @Override
62 | public void onBindViewHolder(GroceryHolder holder, int position) {
63 | Groceries grocery = groceryList.get(position);
64 |
65 | //Sets Text
66 | holder.name.setText(grocery.getName());
67 |
68 | //sets image
69 | Log.e("adapter","image " + grocery.getImage());
70 | String uri = grocery.getImage();
71 | Resources res = holder.image.getContext().getResources();
72 | holder.image.setImageResource(res.getIdentifier(uri, "drawable", BuildConfig.APPLICATION_ID));
73 | //holder.image.setImageResource(grocery.getImage());
74 |
75 | }
76 |
77 |
78 | @Override
79 | public int getItemCount() {
80 | return groceryList.size();
81 | }
82 |
83 |
84 | public void updateList(List newList){
85 | groceryList = newList;
86 | notifyDataSetChanged();
87 | }
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main_home_page.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
19 |
20 |
30 |
31 |
32 |
33 |
40 |
41 |
42 |
51 |
52 |
61 |
62 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ProductsParentViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.graphics.drawable.Drawable;
6 | import android.support.annotation.NonNull;
7 | import android.util.Log;
8 | import android.view.View;
9 | import android.view.animation.RotateAnimation;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 |
13 | /**
14 | * Created by Carson on 2017-11-28.
15 | *
16 | * Feel free to use code just give credit please :)
17 | */
18 |
19 | public class ProductsParentViewHolder extends ParentViewHolder{
20 |
21 | public TextView mTitleTextView;
22 | public ImageView mIconView;
23 | public ImageView mParentDropDownArrow;
24 |
25 | private static final float INITIAL_POSITION = 0.0f;
26 | private static final float ROTATED_POSITION = 180f;
27 |
28 | public ProductsParentViewHolder(View itemView) {
29 | super(itemView);
30 |
31 | mTitleTextView = itemView.findViewById(R.id.name);
32 | mIconView = itemView.findViewById(R.id.image);
33 | mParentDropDownArrow = itemView.findViewById(R.id.parent_list_item_expand_arrow);
34 | }
35 |
36 | public void bind(@NonNull Products product) {
37 |
38 | //set the text of item
39 | mTitleTextView.setText(product.getName());
40 |
41 | //set item image
42 | Resources res = itemView.getContext().getResources();
43 | String uri = product.getImage();
44 | mIconView.setImageResource(res.getIdentifier(uri, "drawable", BuildConfig.APPLICATION_ID));
45 | }
46 |
47 | @Override
48 | public void setExpanded(boolean expanded) {
49 | super.setExpanded(expanded);
50 |
51 | if (expanded) {
52 | mParentDropDownArrow.setRotation(ROTATED_POSITION);
53 | } else {
54 | mParentDropDownArrow.setRotation(INITIAL_POSITION);
55 | }
56 |
57 | }
58 |
59 | @Override
60 | public void onExpansionToggled(boolean expanded) {
61 | super.onExpansionToggled(expanded);
62 |
63 | RotateAnimation rotateAnimation;
64 | if (expanded) { // rotate clockwise
65 | rotateAnimation = new RotateAnimation(ROTATED_POSITION,
66 | INITIAL_POSITION,
67 | RotateAnimation.RELATIVE_TO_SELF, 0.5f,
68 | RotateAnimation.RELATIVE_TO_SELF, 0.5f);
69 | } else { // rotate counterclockwise
70 | rotateAnimation = new RotateAnimation(-1 * ROTATED_POSITION,
71 | INITIAL_POSITION,
72 | RotateAnimation.RELATIVE_TO_SELF, 0.5f,
73 | RotateAnimation.RELATIVE_TO_SELF, 0.5f);
74 | }
75 |
76 | rotateAnimation.setDuration(200);
77 | rotateAnimation.setFillAfter(true);
78 | mParentDropDownArrow.startAnimation(rotateAnimation);
79 |
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/CartItemAdapter.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.util.Log;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.Button;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 |
13 |
14 | import java.util.List;
15 |
16 | /**
17 | * Created by Carson on 2017-11-22.
18 | *
19 | * RecyclerView adapter extended with project-specific required methods
20 | *
21 | * This may all be null and useless now after upgrading my adapter with the ExpandabaleRecyclerAdapter
22 | */
23 |
24 | public class CartItemAdapter extends RecyclerView.Adapter {
25 |
26 | private List cartList;
27 | private LayoutInflater mInflater;
28 |
29 | public CartItemAdapter(List list) {
30 | cartList = list;
31 | //mInflater = LayoutInflater.from(context);
32 |
33 | }
34 |
35 | /* ViewHolder for each cart item */
36 | public class CartHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
37 |
38 | TextView name;
39 | ImageView image;
40 | TextView price;
41 | Button button;
42 |
43 |
44 | CartHolder(View itemView) {
45 | super(itemView);
46 | name = itemView.findViewById(R.id.name);
47 | image = itemView.findViewById(R.id.image);
48 | price = itemView.findViewById(R.id.price);
49 | button = itemView.findViewById(R.id.delete);
50 | }
51 |
52 |
53 |
54 | @Override
55 | public void onClick(View v) {
56 | Log.e("Adapter","On Click");
57 | switch(v.getId()){
58 | case R.id.delete:
59 | Log.e("Adapter","Item Deleted");
60 | break;
61 | }
62 | }
63 | }
64 |
65 | @Override
66 | public CartHolder onCreateViewHolder(ViewGroup parent, int viewType) {
67 | View itemView = LayoutInflater.from(parent.getContext())
68 | .inflate(R.layout.cart_item_layout, parent, false);
69 |
70 |
71 | return new CartHolder(itemView);
72 | }
73 |
74 | @Override
75 | public void onBindViewHolder(CartHolder holder, int position) {
76 | CartItems cartItem = cartList.get(position);
77 |
78 | //Sets Text
79 | holder.name.setText(cartItem.getName());
80 | holder.image.setImageResource(cartItem.getImage());
81 | String total = cartItem.getPrice() + " x " + cartItem.getQuantity();
82 | holder.price.setText(total);
83 | }
84 |
85 |
86 | @Override
87 | public int getItemCount() {
88 | return cartList.size();
89 | }
90 |
91 |
92 | public void updateList(List newList){
93 | cartList = newList;
94 | notifyDataSetChanged();
95 | }
96 |
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/NotificationHandler.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.app.NotificationManager;
4 | import android.app.PendingIntent;
5 | import android.app.TaskStackBuilder;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.support.v4.app.NotificationCompat;
9 | import android.util.Log;
10 |
11 | import java.text.SimpleDateFormat;
12 | import java.util.Calendar;
13 | import java.util.Date;
14 |
15 | /**
16 | * Created by Carson on 2018-01-03.
17 | *
18 | * Feel free to use code just give credit please :)
19 | */
20 |
21 | public class NotificationHandler {
22 |
23 | // Notification handler singleton
24 | private static NotificationHandler nHandler;
25 | private static NotificationManager mNotificationManager;
26 |
27 |
28 | private NotificationHandler () {}
29 |
30 |
31 | /**
32 | * Singleton pattern implementation
33 | * @return
34 | */
35 | public static NotificationHandler getInstance(Context context) {
36 | if(nHandler == null) {
37 | nHandler = new NotificationHandler();
38 | mNotificationManager =
39 | (NotificationManager) context.getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
40 | }
41 |
42 | return nHandler;
43 | }
44 |
45 |
46 | /**
47 | * Shows a simple notification
48 | * @param context aplication context
49 | */
50 | public void createSimpleNotification(Context context) {
51 | // Creates an explicit intent for an Activity
52 | Intent resultIntent = new Intent(context, FinishedActivity.class);
53 |
54 | // Creating a artifical activity stack for the notification activity
55 | TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
56 | stackBuilder.addParentStack(FinishedActivity.class);
57 | stackBuilder.addNextIntent(resultIntent);
58 |
59 | // Pending intent to the notification manager
60 | PendingIntent resultPending = stackBuilder
61 | .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
62 |
63 | Date currentTime = Calendar.getInstance().getTime();
64 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("h:mm a");
65 | simpleDateFormat.format(currentTime);
66 | Log.e("time", " current time is" + currentTime);
67 | String time = String.valueOf(currentTime);
68 |
69 | // Building the notification
70 | NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
71 | .setSmallIcon(R.drawable.ic_launcher_foreground) // notification icon
72 | .setContentTitle("Grocery Purchase Reminder") // main title of the notification
73 | .setContentText("Your order should be ready around " + time) // notification text
74 | .setContentIntent(resultPending); // notification intent
75 |
76 | // mId allows you to update the notification later on.
77 | mNotificationManager.notify(10, mBuilder.build());
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
21 |
22 |
26 |
27 |
32 |
33 |
36 |
37 |
45 |
46 |
47 |
48 |
51 |
52 |
63 |
64 |
65 |
66 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_cart.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
21 |
25 |
26 |
27 |
28 |
33 |
34 |
39 |
40 |
48 |
49 |
50 |
58 |
59 |
67 |
68 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/CartActivity.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Intent;
4 | import android.database.Cursor;
5 | import android.database.sqlite.SQLiteDatabase;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.os.Bundle;
8 | import android.support.v7.widget.DividerItemDecoration;
9 | import android.support.v7.widget.LinearLayoutManager;
10 | import android.support.v7.widget.RecyclerView;
11 | import android.util.Log;
12 | import android.view.View;
13 | import android.widget.Button;
14 | import android.widget.TextView;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | public class CartActivity extends AppCompatActivity {
20 |
21 | Button checkoutButton;
22 | List cartProducts;
23 | CartItems cartItems;
24 |
25 | DatabaseManager dbManager;
26 | //final SQLiteDatabase mDatabase = dbManager.openDatabase();
27 |
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | setContentView(R.layout.activity_cart);
32 |
33 | dbManager = DatabaseManager.getInstance(this);
34 |
35 | RecyclerView recyclerView = findViewById(R.id.recycler_view);
36 |
37 | LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
38 | recyclerView.setLayoutManager(llm);
39 |
40 | DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
41 | llm.getOrientation());
42 | recyclerView.addItemDecoration(dividerItemDecoration);
43 |
44 | //method to build the CartProducts list
45 | cartProducts = getCartData();
46 |
47 | CartItemAdapter mAdapter = new CartItemAdapter(cartProducts);
48 | recyclerView.setAdapter(mAdapter);
49 |
50 | TextView textTotal = findViewById(R.id.textViewTotal);
51 | String total = buildPrice(cartProducts);
52 | textTotal.setText(total);
53 |
54 | //button for ending the activity and moving to transaction completion
55 | checkoutButton = findViewById(R.id.button_checkout);
56 |
57 | checkoutButton.setOnClickListener(new View.OnClickListener() {
58 | @Override
59 | public void onClick(View view) {
60 |
61 | //pass the data
62 |
63 | //clear the data after payment is successful
64 |
65 | //opens payment window
66 | Intent myIntent = new Intent(CartActivity.this, CheckoutActivity.class);
67 |
68 | startActivity(myIntent);
69 |
70 | //purchase made closing screen with eta counter?
71 | }
72 | });
73 | }
74 |
75 | private String buildPrice(List cartProducts) {
76 | Double total = 0.0;
77 | CartItems cartItem;
78 | for(int i = 0; i < cartProducts.size(); i++){
79 | cartItem = cartProducts.get(i);
80 | total += Double.parseDouble(cartItem.getPrice());
81 | }
82 |
83 | return total.toString();
84 | }
85 |
86 | private List getCartData() {
87 | List list = new ArrayList<>();
88 | Integer image = R.mipmap.ic_launcher_round;
89 | cartItems = new CartItems("App1", 0, "19.21", "1");
90 |
91 | //opens a cursor containing all the data from our database Table
92 |
93 | //loop through putting the cursor data into object which are then put into a list
94 | try (Cursor cursor = dbManager.queryAllItems("cart")) {
95 | while (cursor.moveToNext()) {
96 | String data = cursor.getString(1);
97 | Integer data2 = cursor.getInt(2);
98 | String data3 = cursor.getString(3);
99 | String data4 = cursor.getString(4);
100 |
101 | cartItems = new CartItems(data, data2, data3, data4);
102 |
103 | list.add(cartItems);
104 | }
105 | //close the cursor after use.
106 | cursor.close();
107 | }
108 |
109 | return list;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ProductsChildViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.ContentValues;
4 | import android.content.Context;
5 | import android.database.sqlite.SQLiteDatabase;
6 | import android.provider.ContactsContract;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.util.Log;
9 | import android.view.View;
10 | import android.widget.Button;
11 | import android.widget.RelativeLayout;
12 | import android.widget.SeekBar;
13 | import android.widget.TextView;
14 | import android.widget.Toast;
15 |
16 | import java.util.List;
17 |
18 |
19 | /**
20 | * Created by Carson on 2017-11-28.
21 | *
22 | * Feel free to use code just give credit please :)
23 | */
24 |
25 | public class ProductsChildViewHolder extends ChildViewHolder {
26 |
27 | public TextView mDateText;
28 | public Button mAddButton;
29 | public TextView mQuanityText;
30 | public SeekBar mSeekBar;
31 | View view;
32 | // variable to hold context
33 | private Context context;
34 |
35 | public ProductsChildViewHolder(final View itemView) {
36 | super(itemView);
37 |
38 | final DatabaseManager dbManager = DatabaseManager.getInstance(itemView.getContext());
39 | final SQLiteDatabase mDatabase = dbManager.openDatabase();
40 |
41 |
42 | mDateText = itemView.findViewById(R.id.child_list_item_crime_date_text_view);
43 | mAddButton = itemView.findViewById(R.id.add_button);
44 |
45 |
46 | view = itemView.findViewById(R.id.seekBar);
47 |
48 | mQuanityText = view.findViewById(R.id.textViewCustom);
49 | mSeekBar = view.findViewById(R.id.seekBarCustom);
50 |
51 | mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
52 | @Override
53 | public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
54 |
55 | mQuanityText.setText("Quantity: " + i);
56 | }
57 |
58 | @Override
59 | public void onStartTrackingTouch(SeekBar seekBar) {
60 |
61 | }
62 |
63 | @Override
64 | public void onStopTrackingTouch(SeekBar seekBar) {
65 |
66 | }
67 | });
68 |
69 | mAddButton.setOnClickListener(new View.OnClickListener() {
70 | @Override
71 | public void onClick(View view) {
72 |
73 | //add the item to the shopping cart.
74 | //does not work if quantity is zero
75 | String quanityCount = (String) mQuanityText.getText();
76 | String quanityNumber = quanityCount.replaceAll("[^0-9]", "");
77 | int count = Integer.parseInt(quanityNumber);
78 | if (count != 0) {
79 | //give you the position of the parentLayout
80 | int position = getAdapterPosition() - 1;
81 | View view2 = itemView.getRootView();
82 | RecyclerView rv = view2.findViewById(R.id.recycler_view);
83 | ProductsExpandableAdapter mAdapter = (ProductsExpandableAdapter) rv.getAdapter();
84 | List list = mAdapter.getParentItemList();
85 | //the current product you are on
86 | Products products = (Products) list.get(position);
87 |
88 | Log.e("list output", "" + products.getName());
89 |
90 | String priceTag = (String) mDateText.getText();
91 |
92 | //toast
93 | String toastMsg = mDateText.getText() + " Added To Cart";
94 | Toast.makeText(view.getContext(),
95 | toastMsg,
96 | Toast.LENGTH_SHORT)
97 | .show();
98 |
99 | // Add items to cart table to the database
100 | ContentValues values = new ContentValues();
101 | values.put("cartName", products.getName());
102 | values.put("cartImage", products.getImage());
103 | values.put("cartPrice", priceTag);
104 | values.put("cartQuantity", count);
105 | mDatabase.insert("cart", null, values);
106 | Log.e("values", "" + values);
107 | } else {
108 | //toast
109 | String toastMsg = "Quantity must be greater than zero.";
110 | Toast.makeText(view.getContext(),
111 | toastMsg,
112 | Toast.LENGTH_SHORT)
113 | .show();
114 | }
115 | }
116 | });
117 | }
118 |
119 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ParentViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 |
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.View;
6 |
7 | /**
8 | * Created by Carson on 2017-12-05.
9 | *
10 | * Code borrowd from another project
11 | */
12 |
13 | public class ParentViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
14 |
15 | private ParentListItemExpandCollapseListener mParentListItemExpandCollapseListener;
16 | private boolean mExpanded;
17 |
18 | /**
19 | * implementations to be notified of expand/collapse state change events.
20 | */
21 | public interface ParentListItemExpandCollapseListener {
22 |
23 | /**
24 | * Called when a list item is expanded.
25 | *
26 | * @param position The index of the item in the list being expanded
27 | */
28 | void onParentListItemExpanded(int position);
29 |
30 | /**
31 | * Called when a list item is collapsed.
32 | *
33 | * @param position The index of the item in the list being collapsed
34 | */
35 | void onParentListItemCollapsed(int position);
36 | }
37 |
38 | /**
39 | * Default constructor.
40 | *
41 | * @param itemView The {@link View} being hosted in this ViewHolder
42 | */
43 | public ParentViewHolder(View itemView) {
44 | super(itemView);
45 | mExpanded = false;
46 | }
47 |
48 | /**
49 | * Sets a {@link android.view.View.OnClickListener} on the entire parent
50 | * view to trigger expansion.
51 | */
52 | public void setMainItemClickToExpand() {
53 | itemView.setOnClickListener(this);
54 | }
55 |
56 | /**
57 | * corresponding to this {@link ParentViewHolder}.
58 | *
59 | * @return true if expanded, false if not
60 | */
61 | public boolean isExpanded() {
62 | return mExpanded;
63 | }
64 |
65 | /**
66 | * Setter method for expanded state, used for initialization of expanded state.
67 | * changes to the state are given in {@link #onExpansionToggled(boolean)}
68 | *
69 | * @param expanded true if expanded, false if not
70 | */
71 | public void setExpanded(boolean expanded) {
72 | mExpanded = expanded;
73 | }
74 |
75 | /**
76 | * Callback triggered when expansion state is changed, but not during
77 | * initialization.
78 | *
79 | * Useful for implementing animations on expansion.
80 | *
81 | * @param expanded true if view is expanded before expansion is toggled,
82 | * false if not
83 | */
84 | public void onExpansionToggled(boolean expanded) {
85 |
86 | }
87 |
88 | /**
89 | * Getter for the {@link ParentListItemExpandCollapseListener} implemented in
90 | *
91 | * @return The {@link ParentListItemExpandCollapseListener} set in the {@link ParentViewHolder}
92 | */
93 | public ParentListItemExpandCollapseListener getParentListItemExpandCollapseListener() {
94 | return mParentListItemExpandCollapseListener;
95 | }
96 |
97 | /**
98 | * Setter for the {@link ParentListItemExpandCollapseListener} implemented in
99 | *
100 | * @param parentListItemExpandCollapseListener The {@link ParentListItemExpandCollapseListener} to set on the {@link ParentViewHolder}
101 | */
102 | public void setParentListItemExpandCollapseListener(ParentListItemExpandCollapseListener parentListItemExpandCollapseListener) {
103 | mParentListItemExpandCollapseListener = parentListItemExpandCollapseListener;
104 | }
105 |
106 | /**
107 | * {@link android.view.View.OnClickListener} to listen for click events on
108 | * the entire parent {@link View}.
109 | *
110 | * Only registered if {@link #shouldItemViewClickToggleExpansion()} is true.
111 | *
112 | * @param v The {@link View} that is the trigger for expansion
113 | */
114 | @Override
115 | public void onClick(View v) {
116 | if (mExpanded) {
117 | collapseView();
118 | } else {
119 | expandView();
120 | }
121 | }
122 |
123 | /**
124 | * Used to determine whether a click in the entire parent {@link View}
125 | * should trigger row expansion.
126 | *
127 | * If you return false, you can call {@link #expandView()} to trigger an
128 | * expansion in response to a another event or {@link #collapseView()} to
129 | * trigger a collapse.
130 | *
131 | * @return true to set an {@link android.view.View.OnClickListener} on the item view
132 | */
133 | public boolean shouldItemViewClickToggleExpansion() {
134 | return true;
135 | }
136 |
137 | /**
138 | * Triggers expansion of the parent.
139 | */
140 | protected void expandView() {
141 | setExpanded(true);
142 | onExpansionToggled(false);
143 |
144 | if (mParentListItemExpandCollapseListener != null) {
145 | mParentListItemExpandCollapseListener.onParentListItemExpanded(getAdapterPosition());
146 | }
147 | }
148 |
149 | /**
150 | * Triggers collapse of the parent.
151 | */
152 | protected void collapseView() {
153 | setExpanded(false);
154 | onExpansionToggled(true);
155 |
156 | if (mParentListItemExpandCollapseListener != null) {
157 | mParentListItemExpandCollapseListener.onParentListItemCollapsed(getAdapterPosition());
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/GroceryActivity.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Intent;
4 | import android.database.Cursor;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.support.v7.widget.DividerItemDecoration;
8 | import android.support.v7.widget.LinearLayoutManager;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.support.v7.widget.Toolbar;
11 | import android.view.Menu;
12 | import android.view.MenuInflater;
13 | import android.view.MenuItem;
14 | import android.view.View;
15 | import android.widget.ImageButton;
16 | import android.widget.SearchView;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | public class GroceryActivity extends AppCompatActivity {
22 |
23 | //Ui Componenets
24 | RecyclerView recyclerView;
25 | SearchView searchView;
26 | ImageButton cartButton;
27 |
28 | //Adapter
29 | GroceryAdapter myAdapter;
30 |
31 | //list
32 | List list;
33 |
34 | //Database
35 | DatabaseManager dbManager;
36 |
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_shop);
42 |
43 | Toolbar toolbar = findViewById(R.id.toolbar1);
44 | setSupportActionBar(toolbar);
45 |
46 | dbManager = DatabaseManager.getInstance(this);
47 |
48 | //Search Bar
49 | searchView = findViewById(R.id.search_view);
50 |
51 | //Turn iconified to false:
52 | searchView.setIconified(false);
53 | //The above line will expand it to fit the area as well as throw up the keyboard
54 |
55 | //To remove the keyboard, but make sure you keep the expanded version:
56 | searchView.clearFocus();
57 |
58 | list = buildList();
59 |
60 | //Setup Recycler & Adapter
61 | recyclerView = findViewById(R.id.recycler_view);
62 |
63 | LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
64 | recyclerView.setLayoutManager(llm);
65 |
66 | DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
67 | llm.getOrientation());
68 | recyclerView.addItemDecoration(dividerItemDecoration);
69 |
70 | myAdapter = new GroceryAdapter(list);
71 |
72 | recyclerView.setAdapter(myAdapter);
73 |
74 | searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
75 | @Override
76 | public boolean onQueryTextSubmit(String query) {
77 | //Do your search
78 | return false;
79 | }
80 |
81 | @Override
82 | public boolean onQueryTextChange(String newText) {
83 | //update recycler view to display the results
84 | filter(newText);
85 | return true;
86 | }
87 | });
88 |
89 | recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(this, recyclerView, new RecyclerItemClickListener.OnItemClickListener() {
90 | @Override
91 | public void onItemClick(View view, int position) {
92 | // launch InsectDetailsActivity passing information over.
93 | Intent intent = new Intent(view.getContext(), ProductsActivity.class);
94 |
95 | //passing data over to next activity
96 | Groceries grocery = list.get(position);
97 | String nameToPass = grocery.getName();
98 |
99 | intent.putExtra("resultName", nameToPass);
100 |
101 | startActivity(intent);
102 | }
103 |
104 | @Override
105 | public void onLongItemClick(View view, int position) {
106 | // in case LongClick is to later be implemented
107 | }
108 | })
109 | );
110 |
111 |
112 |
113 | /*FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
114 | fab.setOnClickListener(new View.OnClickListener() {
115 | @Override
116 | public void onClick(View view) {
117 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
118 | .setAction("Action", null).show();
119 | }
120 | });*/
121 |
122 | }
123 |
124 |
125 | public boolean onCreateOptionsMenu(Menu menu) {
126 | MenuInflater inflater = getMenuInflater();
127 | inflater.inflate(R.menu.toolbar_menu, menu);
128 | return true;
129 | }
130 |
131 | @Override
132 | public boolean onOptionsItemSelected(MenuItem item) {
133 | switch (item.getItemId()) {
134 | case R.id.action_complete:
135 | // complete workout
136 | Integer int1 = 0;
137 | //Adds new workout to Adapter
138 | Intent myIntent = new Intent(GroceryActivity.this,
139 | CartActivity.class);
140 | startActivityForResult(myIntent, int1);
141 | return true;
142 |
143 | /*case R.id.action_complete:
144 | // User chose the "Favorite" action, mark the current item
145 | // as a favorite...
146 | return true;*/
147 |
148 | default:
149 | // If we got here, the user's action was not recognized.
150 | // Invoke the superclass to handle it.
151 | return super.onOptionsItemSelected(item);
152 |
153 | }
154 | }
155 |
156 |
157 |
158 | private List buildList() {
159 | List list = new ArrayList<>();
160 | Groceries groceries;
161 |
162 | //opens a cursor containing all the data from our database Table
163 |
164 | //loop through putting the cursor data into object which are then put into a list
165 | try (Cursor cursor = dbManager.queryAllItems("groceries")) {
166 | while (cursor.moveToNext()) {
167 | String data = cursor.getString(1);
168 | String data2 = cursor.getString(2);
169 |
170 | groceries = new Groceries(data, data2);
171 |
172 | list.add(groceries);
173 | }
174 | //close the cursor after use.
175 | cursor.close();
176 | }
177 |
178 | return list;
179 | }
180 |
181 | void filter(String text) {
182 | List temp = new ArrayList<>();
183 | for (Groceries d : list) {
184 | //or use .equal(text) with you want equal match
185 | //use .toLowerCase() for better matches
186 | String searchText = text.toLowerCase();
187 | String recycleText = d.getName().toLowerCase();
188 | if (recycleText.contains(searchText)) {
189 | temp.add(d);
190 |
191 | }
192 |
193 | //update recyclerview
194 | myAdapter.updateList(temp);
195 |
196 |
197 | }
198 | }
199 | }
200 |
201 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ProductsActivity.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.Intent;
4 | import android.database.Cursor;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.support.v7.widget.DividerItemDecoration;
8 | import android.support.v7.widget.LinearLayoutManager;
9 | import android.support.v7.widget.RecyclerView;
10 | import android.support.v7.widget.Toolbar;
11 | import android.util.Log;
12 | import android.view.Menu;
13 | import android.view.MenuInflater;
14 | import android.view.MenuItem;
15 | import android.view.View;
16 | import android.widget.ImageButton;
17 | import android.widget.SearchView;
18 |
19 | import java.text.ParseException;
20 | import java.text.SimpleDateFormat;
21 | import java.util.ArrayList;
22 | import java.util.Arrays;
23 | import java.util.Collections;
24 | import java.util.Date;
25 | import java.util.List;
26 |
27 | public class ProductsActivity extends AppCompatActivity {
28 |
29 | //Ui Componenets
30 |
31 | SearchView searchView;
32 |
33 | //Adapter
34 | ProductsExpandableAdapter mAdapter;
35 |
36 | //list
37 | List list;
38 |
39 | //Database
40 | DatabaseManager dbManager;
41 |
42 | @Override
43 | protected void onCreate(Bundle savedInstanceState) {
44 | super.onCreate(savedInstanceState);
45 | setContentView(R.layout.activity_products);
46 | Toolbar toolbar = findViewById(R.id.toolbar);
47 | setSupportActionBar(toolbar);
48 |
49 | dbManager = DatabaseManager.getInstance(this);
50 |
51 | Intent intent = getIntent();
52 | String name = intent.getStringExtra("resultName");
53 |
54 | //Search Bar
55 | searchView = findViewById(R.id.search_view);
56 | //Turn iconified to false:
57 | searchView.setIconified(false);
58 | //The above line will expand it to fit the area as well as throw up the keyboard
59 |
60 | //To remove the keyboard, but make sure you keep the expanded version:
61 | searchView.clearFocus();
62 |
63 | //proper list temporarily generated until a database is built
64 | final List products = generateProductList(name);
65 |
66 | RecyclerView recyclerView = findViewById(R.id.recycler_view);
67 | mAdapter = new ProductsExpandableAdapter(this, products);
68 | mAdapter.setExpandCollapseListener(new ExpandableRecyclerAdapter.ExpandCollapseListener() {
69 | @Override
70 | public void onListItemExpanded(int position) {
71 | Products expandedProducts = products.get(position);
72 |
73 | /* String toastMsg = getResources().getString("expanded", expandedProducts.getName());
74 | Toast.makeText(ProductsActivity.this,
75 | toastMsg,
76 | Toast.LENGTH_SHORT)
77 | .show();*/
78 | }
79 |
80 | @Override
81 | public void onListItemCollapsed(int position) {
82 | Products expandedProducts = products.get(position);
83 |
84 | /* String toastMsg = getResources().getString("expanded", expandedProducts.getName());
85 | Toast.makeText(ProductsActivity.this,
86 | toastMsg,
87 | Toast.LENGTH_SHORT)
88 | .show();*/
89 | }
90 | });
91 |
92 | recyclerView.setAdapter(mAdapter);
93 | // recyclerView.setLayoutManager(new LinearLayoutManager(this));
94 |
95 | LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
96 | recyclerView.setLayoutManager(llm);
97 |
98 | DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
99 | llm.getOrientation());
100 | recyclerView.addItemDecoration(dividerItemDecoration);
101 |
102 |
103 | recyclerView.setLayoutManager(llm);
104 |
105 | }
106 |
107 |
108 | private List generateProductList(String name) {
109 |
110 | List list = new ArrayList<>();
111 | Products products;
112 | ProductsChild productsChild;
113 |
114 | //opens a cursor containing all the data from our database Table
115 |
116 | //loop through putting the cursor data into object which are then put into a list
117 | try (Cursor cursor = dbManager.queryAllItems("products")) {
118 | while (cursor.moveToNext()) {
119 | //if the group matches the grocery name then add this to current list
120 | String data3 = cursor.getString(3);
121 | if (data3.equals(name)) {
122 |
123 | //pull name, image, and price
124 | String data = cursor.getString(1);
125 | String data2 = cursor.getString(2);
126 | Log.e("Cursor"," " + data2);
127 | String price = cursor.getString(4);
128 |
129 | //add child details, then combine to make a product item, finally add to list
130 | productsChild = new ProductsChild(price);
131 | products = new Products(data, data2, Collections.singletonList(productsChild));
132 |
133 | list.add(products);
134 | }
135 | }
136 | //close the cursor after use.
137 | cursor.close();
138 | }
139 |
140 | return list;
141 |
142 |
143 | /* final List productsList = Arrays.asList(appleProduct1, appleProduct1);
144 |
145 | return productsList;*/
146 | }
147 |
148 |
149 | void filter(String text) {
150 | List temp = new ArrayList<>();
151 | for (Products d : list) {
152 | //or use .equal(text) with you want equal match
153 | //use .toLowerCase() for better matches
154 | String searchText = text.toLowerCase();
155 | String recycleText = d.getName().toLowerCase();
156 | if (recycleText.contains(searchText)) {
157 | temp.add(d);
158 |
159 | }
160 | }
161 | //update recyclerview
162 | //productsExpandableAdapter.updateList(temp);
163 |
164 |
165 | }
166 |
167 |
168 | public boolean onCreateOptionsMenu(Menu menu) {
169 | MenuInflater inflater = getMenuInflater();
170 | inflater.inflate(R.menu.toolbar_menu, menu);
171 | return true;
172 | }
173 |
174 | @Override
175 | public boolean onOptionsItemSelected(MenuItem item) {
176 | switch (item.getItemId()) {
177 | case R.id.action_complete:
178 | // complete workout
179 | Integer int1 = 0;
180 | //Adds new workout to Adapter
181 | Intent myIntent = new Intent(ProductsActivity.this,
182 | CartActivity.class);
183 | startActivityForResult(myIntent, int1);
184 | return true;
185 |
186 | /*case R.id.action_complete:
187 | // User chose the "Favorite" action, mark the current item
188 | // as a favorite...
189 | return true;*/
190 |
191 | default:
192 | // If we got here, the user's action was not recognized.
193 | // Invoke the superclass to handle it.
194 | return super.onOptionsItemSelected(item);
195 |
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/DatabaseHelper.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.content.ContentValues;
4 | import android.content.Context;
5 | import android.database.sqlite.SQLiteDatabase;
6 | import android.database.sqlite.SQLiteOpenHelper;
7 |
8 | /**
9 | * Created by Carson on 2017-12-13.
10 | *
11 | * Feel free to use code just give credit please :)
12 | */
13 |
14 | public class DatabaseHelper extends SQLiteOpenHelper {
15 |
16 | // Database Info
17 | private static final String DATABASE_NAME = "groceryDatabase";
18 | private static final int DATABASE_VERSION = 18;
19 |
20 | // Table Names
21 | private static final String TABLE_GROCERIES = "groceries";
22 | private static final String TABLE_PRODUCTS = "products";
23 | private static final String TABLE_CART = "cart";
24 |
25 | // Grocery Table Columns
26 | private static final String KEY_GROCERIES_ID = "id";
27 | private static final String KEY_GROCERIES_NAME = "groceryName";
28 | private static final String KEY_GROCERIES_IMAGE = "groceryImage";
29 |
30 | // User Table Columns
31 | private static final String KEY_PRODUCTS_ID = "id";
32 | private static final String KEY_PRODUCTS_NAME = "productsName";
33 | private static final String KEY_PRODUCTS_IMAGE = "productsImage";
34 | private static final String KEY_PRODUCTS_GROUP = "productsGroup";
35 | private static final String KEY_PRODUCTS_PRICE = "productsPrice";
36 |
37 | // Cart Table Columns
38 | private static final String KEY_CART_ID = "id";
39 | private static final String KEY_CART_NAME = "cartName";
40 | private static final String KEY_CART_IMAGE = "cartImage";
41 | private static final String KEY_CART_PRICE = "cartPrice";
42 | private static final String KEY_CART_QUANTITY = "cartQuantity";
43 |
44 | public DatabaseHelper(Context context) {
45 | super(context, DATABASE_NAME, null, DATABASE_VERSION);
46 | }
47 |
48 | @Override
49 | public void onConfigure(SQLiteDatabase db) {
50 | super.onConfigure(db);
51 | db.setForeignKeyConstraintsEnabled(true);
52 | }
53 |
54 | // Called when the database is created for the FIRST time.
55 | // If a database already exists on disk with the same DATABASE_NAME, this method will NOT be called.
56 | @Override
57 | public void onCreate(SQLiteDatabase db) {
58 | String CREATE_POSTS_TABLE = "CREATE TABLE " + TABLE_GROCERIES +
59 | "(" +
60 | KEY_GROCERIES_ID + " INTEGER PRIMARY KEY," + // Define a primary key
61 | KEY_GROCERIES_NAME + " TEXT, " +
62 | KEY_GROCERIES_IMAGE + " TEXT" +
63 | ")";
64 |
65 | String CREATE_USERS_TABLE = "CREATE TABLE " + TABLE_PRODUCTS +
66 | "(" +
67 | KEY_PRODUCTS_ID + " INTEGER PRIMARY KEY," +
68 | KEY_PRODUCTS_NAME + " TEXT," +
69 | KEY_PRODUCTS_IMAGE + " TEXT," +
70 | KEY_PRODUCTS_GROUP + " TEXT," +
71 | KEY_PRODUCTS_PRICE + " TEXT" +
72 | ")";
73 |
74 | String CREATE_CART_TABLE = "CREATE TABLE " + TABLE_CART +
75 | "(" +
76 | KEY_CART_ID + " INTEGER PRIMARY KEY," +
77 | KEY_CART_NAME + " TEXT," +
78 | KEY_CART_IMAGE + " TEXT," +
79 | KEY_CART_PRICE + " TEXT," +
80 | KEY_CART_QUANTITY + " TEXT" +
81 | ")";
82 |
83 | db.execSQL(CREATE_POSTS_TABLE);
84 | db.execSQL(CREATE_USERS_TABLE);
85 | db.execSQL(CREATE_CART_TABLE);
86 |
87 | addTableData(db);
88 | }
89 |
90 |
91 | @Override
92 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
93 | if (oldVersion != newVersion) {
94 | // Simplest implementation is to drop all old tables and recreate them
95 | db.execSQL("DROP TABLE IF EXISTS " + TABLE_GROCERIES);
96 | db.execSQL("DROP TABLE IF EXISTS " + TABLE_PRODUCTS);
97 | db.execSQL("DROP TABLE IF EXISTS " + TABLE_CART);
98 | onCreate(db);
99 | }
100 | }
101 |
102 |
103 | private void addTableData(SQLiteDatabase db) {
104 | ContentValues values = new ContentValues();
105 |
106 | //add Grocery data to its table
107 |
108 | String[][] data = {{"Fish", "fishicon48"}, {"Bread", "breadicon"}, {"Milk", "milkicon"}, {"Apples", "appleicon"},
109 | {"Oranges", "orangeicon"}, {"Candy", "candyicon"}, {"Soup", "soupicon"}, {"Medicine", "medicineicon"}, {"Pasta", "pastaicon"},
110 | {"Condiments", "condimenticon"}, {"Soft Drinks", "softdrinkicon"}, {"Beef", "beeficon"}, {"Vegetables", "veggieicon"}, {"Cheese", "cheeseicon"}};
111 |
112 | for (String[] aData : data) {
113 | values.put(KEY_GROCERIES_NAME, aData[0]);
114 | values.put(KEY_GROCERIES_IMAGE, aData[1]);
115 |
116 | db.insert(TABLE_GROCERIES, null, values);
117 | }
118 |
119 | //add Product data to its table
120 | ContentValues values2 = new ContentValues();
121 | //ugly example data
122 | String[][] data2 = {{"Johnnys Fish", "fishicon48", "11.95", "Fish"}, {"Pirate Man Fish", "fishicon48", "15.95", "Fish"}, {"Prime AA Fish", "fishicon48", "11.95", "Fish"}, {"Bland Bread", "0", "3.15", "Bread"}
123 | , {"King's Bread", "0", "6.95", "Bread"}, {"Wonder Bread", "0", "3.95", "Bread"}, {"Moo Man Milk", "0", "4.25", "Milk"}, {"Prairie Cow", "0", "4.45", "Milk"}
124 | , {"American Milk", "0", "11.95", "Milk"}, {"Red Apples", "0", "11.95", "Apples"}, {"Round New Yorks", "0", "11.95", "Apples"}, {"Sour Sam Apples", "0", "11.95", "Apples"}
125 | , {"Not Red Oranges", "0", "0.95", "Oranges"}, {"Chinese", "0", "1.10", "Oranges"}, {"Box Oranges", "0", "18.95", "Oranges"}, {"Sour Keys", "0", "0.25", "Candy"}
126 | , {"Blue Whales", "0", "0.25", "Candy"}, {"Pringles", "0", "1.25", "Candy"}, {"Johnnys Fish", "0", "11.95", "Soup"}, {"Johnnys Fish", "0", "11.95", "Soup"}
127 | , {"Johnnys Fish Soup", "0", "11.95", "Soup"}, {"Johnnys Fish", "0", "11.95", "Medicine"}, {"Johnnys Fish", "0", "11.95", "Medicine"}, {"Johnnys Fish", "0", "11.95", "Medicine"}
128 | , {"Johnnys Fish Pasta", "0", "11.95", "Pasta"}, {"Johnnys Fish", "0", "11.95", "Pasta"}, {"Johnnys Fish", "0", "11.95", "Pasta"}, {"Johnnys Fish", "0", "11.95", "Condiments"}
129 | , {"Johnnys Fish", "0", "11.95", "Condiments"}, {"Johnnys Fish", "0", "11.95", "Soft Drinks"}, {"Johnnys Fish", "0", "11.95", "Soft Drinks"}, {"Johnnys Fish", "0", "11.95", "Soft Drinks"}
130 | , {"Johnnys Beef", "0", "11.95", "Beef"}, {"Johnnys Fish", "0", "11.95", "Condiments"}, {"Johnnys Fish", "0", "11.95", "Beef"}, {"Johnnys Fish", "0", "11.95", "Beef"}
131 | , {"Johnnys Fish", "0", "11.95", "Vegetables"}, {"Johnnys Fish", "0", "11.95", "Vegetables"}, {"Johnnys Fish", "0", "11.95", "Vegetables"}, {"Johnnys Fish", "0", "11.95", "Cheese"}
132 | , {"Johnnys Cheese", "0", "11.95", "Cheese"}, {"Johnnys Fish", "0", "11.95", "Cheese"}};
133 |
134 | for (String[] aData : data2) {
135 | values2.put(KEY_PRODUCTS_NAME, aData[0]);
136 | values2.put(KEY_PRODUCTS_IMAGE, aData[1]);
137 | values2.put(KEY_PRODUCTS_PRICE, aData[2]);
138 | values2.put(KEY_PRODUCTS_GROUP, aData[3]);
139 |
140 | db.insert(TABLE_PRODUCTS, null, values2);
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/LoginActivity.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.annotation.TargetApi;
6 | import android.content.Intent;
7 | import android.content.pm.PackageManager;
8 | import android.support.annotation.NonNull;
9 | import android.support.design.widget.Snackbar;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.app.LoaderManager.LoaderCallbacks;
12 |
13 | import android.content.CursorLoader;
14 | import android.content.Loader;
15 | import android.database.Cursor;
16 | import android.net.Uri;
17 | import android.os.AsyncTask;
18 |
19 | import android.os.Build;
20 | import android.os.Bundle;
21 | import android.provider.ContactsContract;
22 | import android.text.TextUtils;
23 | import android.util.Log;
24 | import android.view.KeyEvent;
25 | import android.view.View;
26 | import android.view.View.OnClickListener;
27 | import android.view.inputmethod.EditorInfo;
28 | import android.widget.ArrayAdapter;
29 | import android.widget.AutoCompleteTextView;
30 | import android.widget.Button;
31 | import android.widget.EditText;
32 | import android.widget.TextView;
33 |
34 | import java.util.ArrayList;
35 | import java.util.List;
36 |
37 | import static android.Manifest.permission.READ_CONTACTS;
38 |
39 | /**
40 | * A login screen that offers login via email/password.
41 | */
42 | public class LoginActivity extends AppCompatActivity implements LoaderCallbacks {
43 |
44 | /**
45 | * Id to identity READ_CONTACTS permission request.
46 | */
47 | private static final int REQUEST_READ_CONTACTS = 0;
48 |
49 | /**
50 | * A dummy authentication store containing known user names and passwords.
51 | * TODO: remove after connecting to a real authentication system.
52 | */
53 | private static final String[] DUMMY_CREDENTIALS = new String[]{
54 | "foo@example.com:hello", "bar@example.com:world"
55 | };
56 | /**
57 | * Keep track of the login task to ensure we can cancel it if requested.
58 | */
59 | private UserLoginTask mAuthTask = null;
60 |
61 | // UI references.
62 | private AutoCompleteTextView mEmailView;
63 | private EditText mPasswordView;
64 | private View mProgressView;
65 | private View mLoginFormView;
66 |
67 | @Override
68 | protected void onCreate(Bundle savedInstanceState) {
69 | super.onCreate(savedInstanceState);
70 | setContentView(R.layout.activity_login);
71 | // Set up the login form.
72 | mEmailView = findViewById(R.id.email);
73 | populateAutoComplete();
74 |
75 | mPasswordView = findViewById(R.id.password);
76 | mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
77 | @Override
78 | public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
79 | if (id == EditorInfo.IME_ACTION_DONE || id == EditorInfo.IME_NULL) {
80 | attemptLogin();
81 | return true;
82 | }
83 | return false;
84 | }
85 | });
86 |
87 | Button mEmailSignInButton = findViewById(R.id.email_sign_in_button);
88 | mEmailSignInButton.setOnClickListener(new OnClickListener() {
89 | @Override
90 | public void onClick(View view) {
91 | attemptLogin();
92 | }
93 | });
94 |
95 | mLoginFormView = findViewById(R.id.login_form);
96 | mProgressView = findViewById(R.id.login_progress);
97 | }
98 |
99 | private void populateAutoComplete() {
100 | if (!mayRequestContacts()) {
101 | return;
102 | }
103 |
104 | getLoaderManager().initLoader(0, null, this);
105 | }
106 |
107 | private boolean mayRequestContacts() {
108 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
109 | return true;
110 | }
111 | if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
112 | return true;
113 | }
114 | if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
115 | Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
116 | .setAction(android.R.string.ok, new View.OnClickListener() {
117 | @Override
118 | @TargetApi(Build.VERSION_CODES.M)
119 | public void onClick(View v) {
120 | requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
121 | }
122 | });
123 | } else {
124 | requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
125 | }
126 | return false;
127 | }
128 |
129 | /**
130 | * Callback received when a permissions request has been completed.
131 | */
132 | @Override
133 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
134 | @NonNull int[] grantResults) {
135 | Log.e("Login","Attempted");
136 | if (requestCode == REQUEST_READ_CONTACTS) {
137 | if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
138 | Log.e("Login","Complete");
139 | populateAutoComplete();
140 | }
141 | }
142 | }
143 |
144 |
145 | /**
146 | * Attempts to sign in or register the account specified by the login form.
147 | * If there are form errors (invalid email, missing fields, etc.), the
148 | * errors are presented and no actual login attempt is made.
149 | */
150 | private void attemptLogin() {
151 | if (mAuthTask != null) {
152 | return;
153 | }
154 |
155 | // Reset errors.
156 | mEmailView.setError(null);
157 | mPasswordView.setError(null);
158 |
159 | // Store values at the time of the login attempt.
160 | String email = mEmailView.getText().toString();
161 | String password = mPasswordView.getText().toString();
162 |
163 | boolean cancel = false;
164 | View focusView = null;
165 |
166 | // Check for a valid password, if the user entered one.
167 | if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
168 | mPasswordView.setError(getString(R.string.error_invalid_password));
169 | focusView = mPasswordView;
170 | cancel = true;
171 | }
172 |
173 | // Check for a valid email address.
174 | if (TextUtils.isEmpty(email)) {
175 | mEmailView.setError(getString(R.string.error_field_required));
176 | focusView = mEmailView;
177 | cancel = true;
178 | } else if (!isEmailValid(email)) {
179 | mEmailView.setError(getString(R.string.error_invalid_email));
180 | focusView = mEmailView;
181 | cancel = true;
182 | }
183 |
184 | if (cancel) {
185 | // There was an error; don't attempt login and focus the first
186 | // form field with an error.
187 | focusView.requestFocus();
188 | } else {
189 | // Show a progress spinner, and kick off a background task to
190 | // perform the user login attempt.
191 | Log.e("Login","Complete");
192 | showProgress(true);
193 | mAuthTask = new UserLoginTask(email, password);
194 | mAuthTask.execute((Void) null);
195 |
196 | }
197 | }
198 |
199 | private boolean isEmailValid(String email) {
200 | //TODO: Replace this with your own logic
201 | return email.contains("@");
202 | }
203 |
204 | private boolean isPasswordValid(String password) {
205 | //TODO: Replace this with your own logic
206 | return password.length() > 4;
207 | }
208 |
209 | /**
210 | * Shows the progress UI and hides the login form.
211 | */
212 | @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
213 | private void showProgress(final boolean show) {
214 | // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
215 | // for very easy animations. If available, use these APIs to fade-in
216 | // the progress spinner.
217 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
218 | int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
219 |
220 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
221 | mLoginFormView.animate().setDuration(shortAnimTime).alpha(
222 | show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
223 | @Override
224 | public void onAnimationEnd(Animator animation) {
225 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
226 | }
227 | });
228 |
229 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
230 | mProgressView.animate().setDuration(shortAnimTime).alpha(
231 | show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
232 | @Override
233 | public void onAnimationEnd(Animator animation) {
234 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
235 | }
236 | });
237 | } else {
238 | // The ViewPropertyAnimator APIs are not available, so simply show
239 | // and hide the relevant UI components.
240 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
241 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
242 | }
243 | }
244 |
245 | @Override
246 | public Loader onCreateLoader(int i, Bundle bundle) {
247 | return new CursorLoader(this,
248 | // Retrieve data rows for the device user's 'profile' contact.
249 | Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
250 | ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
251 |
252 | // Select only email addresses.
253 | ContactsContract.Contacts.Data.MIMETYPE +
254 | " = ?", new String[]{ContactsContract.CommonDataKinds.Email
255 | .CONTENT_ITEM_TYPE},
256 |
257 | // Show primary email addresses first. Note that there won't be
258 | // a primary email address if the user hasn't specified one.
259 | ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
260 | }
261 |
262 | @Override
263 | public void onLoadFinished(Loader cursorLoader, Cursor cursor) {
264 | List emails = new ArrayList<>();
265 | cursor.moveToFirst();
266 | while (!cursor.isAfterLast()) {
267 | emails.add(cursor.getString(ProfileQuery.ADDRESS));
268 | cursor.moveToNext();
269 | }
270 |
271 | addEmailsToAutoComplete(emails);
272 | }
273 |
274 | @Override
275 | public void onLoaderReset(Loader cursorLoader) {
276 |
277 | }
278 |
279 | private void addEmailsToAutoComplete(List emailAddressCollection) {
280 | //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
281 | ArrayAdapter adapter =
282 | new ArrayAdapter<>(LoginActivity.this,
283 | android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
284 |
285 | mEmailView.setAdapter(adapter);
286 | }
287 |
288 |
289 | private interface ProfileQuery {
290 | String[] PROJECTION = {
291 | ContactsContract.CommonDataKinds.Email.ADDRESS,
292 | ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
293 | };
294 |
295 | int ADDRESS = 0;
296 | int IS_PRIMARY = 1;
297 | }
298 |
299 | /**
300 | * Represents an asynchronous login/registration task used to authenticate
301 | * the user.
302 | */
303 | public class UserLoginTask extends AsyncTask {
304 |
305 | private final String mEmail;
306 | private final String mPassword;
307 |
308 | UserLoginTask(String email, String password) {
309 | mEmail = email;
310 | mPassword = password;
311 | }
312 |
313 | @Override
314 | protected Boolean doInBackground(Void... params) {
315 | // TODO: attempt authentication against a network service.
316 |
317 | try {
318 | // Simulate network access.
319 | Thread.sleep(2000);
320 | } catch (InterruptedException e) {
321 | return false;
322 | }
323 |
324 | for (String credential : DUMMY_CREDENTIALS) {
325 | String[] pieces = credential.split(":");
326 | if (pieces[0].equals(mEmail)) {
327 | // Account exists, return true if the password matches.
328 | return pieces[1].equals(mPassword);
329 | }
330 | }
331 |
332 | // TODO: register the new account here.
333 | return true;
334 | }
335 |
336 | @Override
337 | protected void onPostExecute(final Boolean success) {
338 | mAuthTask = null;
339 | showProgress(false);
340 |
341 | if (success) {
342 | //finish();
343 | Intent i = new Intent(LoginActivity.this, MainHomePageActivity.class);
344 | startActivity(i);
345 | } else {
346 | mPasswordView.setError(getString(R.string.error_incorrect_password));
347 | mPasswordView.requestFocus();
348 | }
349 | }
350 |
351 | @Override
352 | protected void onCancelled() {
353 | mAuthTask = null;
354 | showProgress(false);
355 | }
356 | }
357 | }
358 |
359 |
--------------------------------------------------------------------------------
/app/src/main/java/com/carsonskjerdal/app/groceryshop/ExpandableRecyclerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.carsonskjerdal.app.groceryshop;
2 |
3 | /**
4 | * Created by Carson on 2017-12-05.
5 | *
6 | * Feel free to use code just give credit please :)
7 | */
8 |
9 |
10 | import android.app.Activity;
11 | import android.os.Bundle;
12 | import android.support.v7.widget.RecyclerView;
13 | import android.view.ViewGroup;
14 | import java.util.ArrayList;
15 | import java.util.HashMap;
16 | import java.util.List;
17 |
18 | /**
19 | * RecyclerView.Adapter implementation that
20 | * adds the ability to expand and collapse list items.
21 | *
22 | * Changes should be notified through:
23 | * {@link #notifyParentItemInserted(int)}
24 | * {@link #notifyParentItemRemoved(int)}
25 | * {@link #notifyParentItemChanged(int)}
26 | * {@link #notifyParentItemRangeInserted(int, int)}
27 | * {@link #notifyChildItemInserted(int, int)}
28 | * {@link #notifyChildItemRemoved(int, int)}
29 | * {@link #notifyChildItemChanged(int, int)}
30 | * methods and not the notify methods of RecyclerView.Adapter.
31 | *
32 | * @author Ryan Brooks
33 | * @version 1.0
34 | * @since 5/27/2015
35 | */
36 | public abstract class ExpandableRecyclerAdapter
37 | extends RecyclerView.Adapter implements ParentViewHolder.ParentListItemExpandCollapseListener {
38 |
39 | private static final String EXPANDED_STATE_MAP = "ExpandableRecyclerAdapter.ExpandedStateMap";
40 | private static final int TYPE_PARENT = 0;
41 | private static final int TYPE_CHILD = 1;
42 |
43 | /**
44 | * A {@link List} of all currently expanded {@link ParentListItem} objects
45 | * and their children, in order. Changes to this list should be made through the add/remove methods
46 | * available in {@link ExpandableRecyclerAdapter}
47 | */
48 | protected List mItemList;
49 |
50 | private List extends ParentListItem> mParentItemList;
51 | private ExpandCollapseListener mExpandCollapseListener;
52 | private List mAttachedRecyclerViewPool;
53 |
54 | /**
55 | * Allows objects to register themselves as expand/collapse listeners to be
56 | * notified of change events.
57 | *
58 | * Implement this in your {@link android.app.Activity} or {@link android.app.Fragment}
59 | * to receive these callbacks.
60 | */
61 | public interface ExpandCollapseListener {
62 |
63 | /**
64 | * Called when a list item is expanded.
65 | *
66 | * @param position The index of the item in the list being expanded
67 | */
68 | void onListItemExpanded(int position);
69 |
70 | /**
71 | * Called when a list item is collapsed.
72 | *
73 | * @param position The index of the item in the list being collapsed
74 | */
75 | void onListItemCollapsed(int position);
76 | }
77 |
78 | /**
79 | * Primary constructor. Sets up {@link #mParentItemList} and {@link #mItemList}.
80 | *
81 | * Changes to {@link #mParentItemList} should be made through add/remove methods in
82 | * {@link ExpandableRecyclerAdapter}
83 | *
84 | * @param parentItemList List of all {@link ParentListItem} objects to be
85 | * displayed in the RecyclerView that this
86 | * adapter is linked to
87 | */
88 | public ExpandableRecyclerAdapter(List parentItemList) {
89 | super();
90 | mParentItemList = parentItemList;
91 | mItemList = ExpandableRecyclerAdapterHelper.generateParentChildItemList(parentItemList);
92 | mAttachedRecyclerViewPool = new ArrayList<>();
93 | }
94 |
95 | /**
96 | * Implementation of Adapter.onCreateViewHolder(ViewGroup, int)
97 | * that determines if the list item is a parent or a child and calls through
98 | * to the appropriate implementation of either {@link #onCreateParentViewHolder(ViewGroup)}
99 | * or {@link #onCreateChildViewHolder(ViewGroup)}.
100 | *
101 | * @param viewGroup The {@link ViewGroup} into which the new {@link android.view.View}
102 | * will be added after it is bound to an adapter position.
103 | * @param viewType The view type of the new {@code android.view.View}.
104 | * @return A new RecyclerView.ViewHolder
105 | * that holds a {@code android.view.View} of the given view type.
106 | */
107 | @Override
108 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
109 | if (viewType == TYPE_PARENT) {
110 | PVH pvh = onCreateParentViewHolder(viewGroup);
111 | pvh.setParentListItemExpandCollapseListener(this);
112 | return pvh;
113 | } else if (viewType == TYPE_CHILD) {
114 | return onCreateChildViewHolder(viewGroup);
115 | } else {
116 | throw new IllegalStateException("Incorrect ViewType found");
117 | }
118 | }
119 |
120 | /**
121 | * Implementation of Adapter.onBindViewHolder(RecyclerView.ViewHolder, int)
122 | * that determines if the list item is a parent or a child and calls through
123 | * to the appropriate implementation of either {@link #onBindParentViewHolder(ParentViewHolder, int, ParentListItem)}
124 | * or {@link #onBindChildViewHolder(ChildViewHolder, int, Object)}.
125 | *
126 | * @param holder The RecyclerView.ViewHolder to bind data to
127 | * @param position The index in the list at which to bind
128 | * @throws IllegalStateException if the item in the list is either null or
129 | * not of type {@link ParentListItem}
130 | */
131 | @Override
132 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
133 | Object listItem = getListItem(position);
134 | if (listItem instanceof ParentWrapper) {
135 | PVH parentViewHolder = (PVH) holder;
136 |
137 | if (parentViewHolder.shouldItemViewClickToggleExpansion()) {
138 | parentViewHolder.setMainItemClickToExpand();
139 | }
140 |
141 | ParentWrapper parentWrapper = (ParentWrapper) listItem;
142 | parentViewHolder.setExpanded(parentWrapper.isExpanded());
143 | onBindParentViewHolder(parentViewHolder, position, parentWrapper.getParentListItem());
144 | } else if (listItem == null) {
145 | throw new IllegalStateException("Incorrect ViewHolder found");
146 | } else {
147 | onBindChildViewHolder((CVH) holder, position, listItem);
148 | }
149 | }
150 |
151 | /**
152 | * Callback called from {@link #onCreateViewHolder(ViewGroup, int)} when
153 | * the list item created is a parent.
154 | *
155 | * @param parentViewGroup The {@link ViewGroup} in the list for which a {@link PVH}
156 | * is being created
157 | * @return A {@code PVH} corresponding to the {@link ParentListItem} with
158 | * the {@code ViewGroup} parentViewGroup
159 | */
160 | public abstract PVH onCreateParentViewHolder(ViewGroup parentViewGroup);
161 |
162 | /**
163 | * Callback called from {@link #onCreateViewHolder(ViewGroup, int)} when
164 | * the list item created is a child.
165 | *
166 | * @param childViewGroup The {@link ViewGroup} in the list for which a {@link CVH}
167 | * is being created
168 | * @return A {@code CVH} corresponding to the child list item with the
169 | * {@code ViewGroup} childViewGroup
170 | */
171 | public abstract CVH onCreateChildViewHolder(ViewGroup childViewGroup);
172 |
173 | /**
174 | * Callback called from onBindViewHolder(RecyclerView.ViewHolder, int)
175 | * when the list item bound to is a parent.
176 | *
177 | * Bind data to the {@link PVH} here.
178 | *
179 | * @param parentViewHolder The {@code PVH} to bind data to
180 | * @param position The index in the list at which to bind
181 | * @param parentListItem The {@link ParentListItem} which holds the data to
182 | * be bound to the {@code PVH}
183 | */
184 | public abstract void onBindParentViewHolder(PVH parentViewHolder, int position, ParentListItem parentListItem);
185 |
186 | /**
187 | * Callback called from onBindViewHolder(RecyclerView.ViewHolder, int)
188 | * when the list item bound to is a child.
189 | *
190 | * Bind data to the {@link CVH} here.
191 | *
192 | * @param childViewHolder The {@code CVH} to bind data to
193 | * @param position The index in the list at which to bind
194 | * @param childListItem The child list item which holds that data to be
195 | * bound to the {@code CVH}
196 | */
197 | public abstract void onBindChildViewHolder(CVH childViewHolder, int position, Object childListItem);
198 |
199 | /**
200 | * Gets the number of parent and child objects currently expanded.
201 | *
202 | * @return The size of {@link #mItemList}
203 | */
204 | @Override
205 | public int getItemCount() {
206 | return mItemList.size();
207 | }
208 |
209 | /**
210 | * Gets the view type of the item at the given position.
211 | *
212 | * @param position The index in the list to get the view type of
213 | * @return {@value #TYPE_PARENT} for {@link ParentListItem} and {@value #TYPE_CHILD}
214 | * for child list items
215 | * @throws IllegalStateException if the item at the given position in the list is null
216 | */
217 | @Override
218 | public int getItemViewType(int position) {
219 | Object listItem = getListItem(position);
220 | if (listItem instanceof ParentWrapper) {
221 | return TYPE_PARENT;
222 | } else if (listItem == null) {
223 | throw new IllegalStateException("Null object added");
224 | } else {
225 | return TYPE_CHILD;
226 | }
227 | }
228 |
229 | /**
230 | * Gets the list of ParentItems that is backing this adapter.
231 | * Changes can be made to the list and the adapter notified via the
232 | * {@link #notifyParentItemInserted(int)}
233 | * {@link #notifyParentItemRemoved(int)}
234 | * {@link #notifyParentItemChanged(int)}
235 | * {@link #notifyParentItemRangeInserted(int, int)}
236 | * {@link #notifyChildItemInserted(int, int)}
237 | * {@link #notifyChildItemRemoved(int, int)}
238 | * {@link #notifyChildItemChanged(int, int)}
239 | * methods.
240 | *
241 | *
242 | * @return The list of ParentListItems that this adapter represents
243 | */
244 | public List extends ParentListItem> getParentItemList() {
245 | return mParentItemList;
246 | }
247 |
248 | /**
249 | *
250 | * Called when a {@link ParentListItem} is triggered to expand.
251 | *
252 | * @param position The index of the item in the list being expanded
253 | */
254 | @Override
255 | public void onParentListItemExpanded(int position) {
256 | Object listItem = getListItem(position);
257 | if (listItem instanceof ParentWrapper) {
258 | expandParentListItem((ParentWrapper) listItem, position, true);
259 | }
260 | }
261 |
262 | /**
263 | *
264 | * Called when a {@link ParentListItem} is triggered to collapse.
265 | *
266 | * @param position The index of the item in the list being collapsed
267 | */
268 | @Override
269 | public void onParentListItemCollapsed(int position) {
270 | Object listItem = getListItem(position);
271 | if (listItem instanceof ParentWrapper) {
272 | collapseParentListItem((ParentWrapper) listItem, position, true);
273 | }
274 | }
275 |
276 | /**
277 | * Implementation of Adapter#onAttachedToRecyclerView(RecyclerView).
278 | *
279 | * Called when this {@link ExpandableRecyclerAdapter} is attached to a RecyclerView.
280 | *
281 | * @param recyclerView The {@code RecyclerView} this {@code ExpandableRecyclerAdapter}
282 | * is being attached to
283 | */
284 | @Override
285 | public void onAttachedToRecyclerView(RecyclerView recyclerView) {
286 | super.onAttachedToRecyclerView(recyclerView);
287 | mAttachedRecyclerViewPool.add(recyclerView);
288 | }
289 |
290 | /**
291 | * Implementation of Adapter.onDetachedFromRecyclerView(RecyclerView)
292 | *
293 | * Called when this ExpandableRecyclerAdapter is detached from a RecyclerView.
294 | *
295 | * @param recyclerView The {@code RecyclerView} this {@code ExpandableRecyclerAdapter}
296 | * is being detached from
297 | */
298 | @Override
299 | public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
300 | super.onDetachedFromRecyclerView(recyclerView);
301 | mAttachedRecyclerViewPool.remove(recyclerView);
302 | }
303 |
304 | public void setExpandCollapseListener(ExpandCollapseListener expandCollapseListener) {
305 | mExpandCollapseListener = expandCollapseListener;
306 | }
307 |
308 | // region Programmatic Expansion/Collapsing
309 |
310 | /**
311 | * Expands the parent with the specified index in the list of parents.
312 | *
313 | * @param parentIndex The index of the parent to expand
314 | */
315 | public void expandParent(int parentIndex) {
316 | int parentWrapperIndex = getParentWrapperIndex(parentIndex);
317 |
318 | Object listItem = getListItem(parentWrapperIndex);
319 | ParentWrapper parentWrapper;
320 | if (listItem instanceof ParentWrapper) {
321 | parentWrapper = (ParentWrapper) listItem;
322 | } else {
323 | return;
324 | }
325 |
326 | expandViews(parentWrapper, parentWrapperIndex);
327 | }
328 |
329 | /**
330 | * Expands the parent associated with a specified {@link ParentListItem} in
331 | * the list of parents.
332 | *
333 | * @param parentListItem The {@code ParentListItem} of the parent to expand
334 | */
335 | public void expandParent(ParentListItem parentListItem) {
336 | ParentWrapper parentWrapper = getParentWrapper(parentListItem);
337 | int parentWrapperIndex = mItemList.indexOf(parentWrapper);
338 | if (parentWrapperIndex == -1) {
339 | return;
340 | }
341 |
342 | expandViews(parentWrapper, parentWrapperIndex);
343 | }
344 |
345 | /**
346 | * Expands all parents in a range of indices in the list of parents.
347 | *
348 | * @param startParentIndex The index at which to to start expanding parents
349 | * @param parentCount The number of parents to expand
350 | */
351 | public void expandParentRange(int startParentIndex, int parentCount) {
352 | int endParentIndex = startParentIndex + parentCount;
353 | for (int i = startParentIndex; i < endParentIndex; i++) {
354 | expandParent(i);
355 | }
356 | }
357 |
358 | /**
359 | * Expands all parents in the list.
360 | */
361 | public void expandAllParents() {
362 | for (ParentListItem parentListItem : mParentItemList) {
363 | expandParent(parentListItem);
364 | }
365 | }
366 |
367 | /**
368 | * Collapses the parent with the specified index in the list of parents.
369 | *
370 | * @param parentIndex The index of the parent to collapse
371 | */
372 | public void collapseParent(int parentIndex) {
373 | int parentWrapperIndex = getParentWrapperIndex(parentIndex);
374 |
375 | Object listItem = getListItem(parentWrapperIndex);
376 | ParentWrapper parentWrapper;
377 | if (listItem instanceof ParentWrapper) {
378 | parentWrapper = (ParentWrapper) listItem;
379 | } else {
380 | return;
381 | }
382 |
383 | collapseViews(parentWrapper, parentWrapperIndex);
384 | }
385 |
386 | /**
387 | * Collapses the parent associated with a specified {@link ParentListItem} in
388 | * the list of parents.
389 | *
390 | * @param parentListItem The {@code ParentListItem} of the parent to collapse
391 | */
392 | public void collapseParent(ParentListItem parentListItem) {
393 | ParentWrapper parentWrapper = getParentWrapper(parentListItem);
394 | int parentWrapperIndex = mItemList.indexOf(parentWrapper);
395 | if (parentWrapperIndex == -1) {
396 | return;
397 | }
398 |
399 | collapseViews(parentWrapper, parentWrapperIndex);
400 | }
401 |
402 | /**
403 | * Collapses all parents in a range of indices in the list of parents.
404 | *
405 | * @param startParentIndex The index at which to to start collapsing parents
406 | * @param parentCount The number of parents to collapse
407 | */
408 | public void collapseParentRange(int startParentIndex, int parentCount) {
409 | int endParentIndex = startParentIndex + parentCount;
410 | for (int i = startParentIndex; i < endParentIndex; i++) {
411 | collapseParent(i);
412 | }
413 | }
414 |
415 | /**
416 | * Collapses all parents in the list.
417 | */
418 | public void collapseAllParents() {
419 | for (ParentListItem parentListItem : mParentItemList) {
420 | collapseParent(parentListItem);
421 | }
422 | }
423 |
424 | /**
425 | * Stores the expanded state map across state loss.
426 | *
427 | * Should be called from {@link Activity#onSaveInstanceState(Bundle)} in
428 | * the {@link Activity} that hosts the RecyclerView that this
429 | * {@link ExpandableRecyclerAdapter} is attached to.
430 | *
431 | * This will make sure to add the expanded state map as an extra to the
432 | * instance state bundle to be used in {@link #onRestoreInstanceState(Bundle)}.
433 | *
434 | * @param savedInstanceState The {@code Bundle} into which to store the
435 | * expanded state map
436 | */
437 | public void onSaveInstanceState(Bundle savedInstanceState) {
438 | savedInstanceState.putSerializable(EXPANDED_STATE_MAP, generateExpandedStateMap());
439 | }
440 |
441 | /**
442 | * Fetches the expandable state map from the saved instance state {@link Bundle}
443 | * and restores the expanded states of all of the list items.
444 | *
445 | * Should be called from {@link Activity#onRestoreInstanceState(Bundle)} in
446 | * the {@link Activity} that hosts the RecyclerView that this
447 | * {@link ExpandableRecyclerAdapter} is attached to.
448 | *
449 | * Assumes that the list of parent list items is the same as when the saved
450 | * instance state was stored.
451 | *
452 | * @param savedInstanceState The {@code Bundle} from which the expanded
453 | * state map is loaded
454 | */
455 | public void onRestoreInstanceState(Bundle savedInstanceState) {
456 | if (savedInstanceState == null
457 | || !savedInstanceState.containsKey(EXPANDED_STATE_MAP)) {
458 | return;
459 | }
460 |
461 | HashMap expandedStateMap = (HashMap) savedInstanceState.getSerializable(EXPANDED_STATE_MAP);
462 | if (expandedStateMap == null) {
463 | return;
464 | }
465 |
466 | List parentWrapperList = new ArrayList<>();
467 | ParentListItem parentListItem;
468 | ParentWrapper parentWrapper;
469 |
470 | int parentListItemCount = mParentItemList.size();
471 | for (int i = 0; i < parentListItemCount; i++) {
472 | parentListItem = mParentItemList.get(i);
473 | parentWrapper = new ParentWrapper(parentListItem);
474 | parentWrapperList.add(parentWrapper);
475 |
476 | if (expandedStateMap.containsKey(i)) {
477 | boolean expanded = expandedStateMap.get(i);
478 | if (expanded) {
479 | parentWrapper.setExpanded(true);
480 |
481 | int childListItemCount = parentWrapper.getChildItemList().size();
482 | for (int j = 0; j < childListItemCount; j++) {
483 | parentWrapperList.add(parentWrapper.getChildItemList().get(j));
484 | }
485 | }
486 | }
487 | }
488 |
489 | mItemList = parentWrapperList;
490 |
491 | notifyDataSetChanged();
492 | }
493 |
494 | /**
495 | * Gets the list item held at the specified adapter position.
496 | *
497 | * @param position The index of the list item to return
498 | * @return The list item at the specified position
499 | */
500 | protected Object getListItem(int position) {
501 | boolean indexInRange = position >= 0 && position < mItemList.size();
502 | if (indexInRange) {
503 | return mItemList.get(position);
504 | } else {
505 | return null;
506 | }
507 | }
508 |
509 | /**
510 | * Calls through to the ParentViewHolder to expand views for each
511 | * RecyclerView the specified parent is a child of.
512 | *
513 | * These calls to the ParentViewHolder are made so that animations can be
514 | * triggered at the ViewHolder level.
515 | *
516 | * @param parentIndex The index of the parent to expand
517 | */
518 | private void expandViews(ParentWrapper parentWrapper, int parentIndex) {
519 | PVH viewHolder;
520 | for (RecyclerView recyclerView : mAttachedRecyclerViewPool) {
521 | viewHolder = (PVH) recyclerView.findViewHolderForAdapterPosition(parentIndex);
522 | if (viewHolder != null && !viewHolder.isExpanded()) {
523 | viewHolder.setExpanded(true);
524 | viewHolder.onExpansionToggled(false);
525 | }
526 |
527 | expandParentListItem(parentWrapper, parentIndex, false);
528 | }
529 | }
530 |
531 | /**
532 | * Calls through to the ParentViewHolder to collapse views for each
533 | * RecyclerView a specified parent is a child of.
534 | *
535 | * These calls to the ParentViewHolder are made so that animations can be
536 | * triggered at the ViewHolder level.
537 | *
538 | * @param parentIndex The index of the parent to collapse
539 | */
540 | private void collapseViews(ParentWrapper parentWrapper, int parentIndex) {
541 | PVH viewHolder;
542 | for (RecyclerView recyclerView : mAttachedRecyclerViewPool) {
543 | viewHolder = (PVH) recyclerView.findViewHolderForAdapterPosition(parentIndex);
544 | if (viewHolder != null && viewHolder.isExpanded()) {
545 | viewHolder.setExpanded(false);
546 | viewHolder.onExpansionToggled(true);
547 | }
548 |
549 | collapseParentListItem(parentWrapper, parentIndex, false);
550 | }
551 | }
552 |
553 | /**
554 | * Expands a specified parent item. Calls through to the
555 | * ExpandCollapseListener and adds children of the specified parent to the
556 | * total list of items.
557 | *
558 | * @param parentWrapper The ParentWrapper of the parent to expand
559 | * @param parentIndex The index of the parent to expand
560 | * @param expansionTriggeredByListItemClick true if expansion was triggered
561 | * by a click event, false otherwise.
562 | */
563 | private void expandParentListItem(ParentWrapper parentWrapper, int parentIndex, boolean expansionTriggeredByListItemClick) {
564 | if (!parentWrapper.isExpanded()) {
565 | parentWrapper.setExpanded(true);
566 |
567 | List> childItemList = parentWrapper.getChildItemList();
568 | if (childItemList != null) {
569 | int childListItemCount = childItemList.size();
570 | for (int i = 0; i < childListItemCount; i++) {
571 | mItemList.add(parentIndex + i + 1, childItemList.get(i));
572 | }
573 |
574 | notifyItemRangeInserted(parentIndex + 1, childListItemCount);
575 | }
576 |
577 | if (expansionTriggeredByListItemClick && mExpandCollapseListener != null) {
578 | int expandedCountBeforePosition = getExpandedItemCount(parentIndex);
579 | mExpandCollapseListener.onListItemExpanded(parentIndex - expandedCountBeforePosition);
580 | }
581 | }
582 | }
583 |
584 | /**
585 | * Collapses a specified parent item. Calls through to the
586 | * ExpandCollapseListener and adds children of the specified parent to the
587 | * total list of items.
588 | *
589 | * @param parentWrapper The ParentWrapper of the parent to collapse
590 | * @param parentIndex The index of the parent to collapse
591 | * @param collapseTriggeredByListItemClick true if expansion was triggered
592 | * by a click event, false otherwise.
593 | */
594 | private void collapseParentListItem(ParentWrapper parentWrapper, int parentIndex, boolean collapseTriggeredByListItemClick) {
595 | if (parentWrapper.isExpanded()) {
596 | parentWrapper.setExpanded(false);
597 |
598 | List> childItemList = parentWrapper.getChildItemList();
599 | if (childItemList != null) {
600 | int childListItemCount = childItemList.size();
601 | for (int i = childListItemCount - 1; i >= 0; i--) {
602 | mItemList.remove(parentIndex + i + 1);
603 | }
604 |
605 | notifyItemRangeRemoved(parentIndex + 1, childListItemCount);
606 | }
607 |
608 | if (collapseTriggeredByListItemClick && mExpandCollapseListener != null) {
609 | int expandedCountBeforePosition = getExpandedItemCount(parentIndex);
610 | mExpandCollapseListener.onListItemCollapsed(parentIndex - expandedCountBeforePosition);
611 | }
612 | }
613 | }
614 |
615 | /**
616 | * Gets the number of expanded child list items before the specified position.
617 | *
618 | * @param position The index before which to return the number of expanded
619 | * child list items
620 | * @return The number of expanded child list items before the specified position
621 | */
622 | private int getExpandedItemCount(int position) {
623 | if (position == 0) {
624 | return 0;
625 | }
626 |
627 | int expandedCount = 0;
628 | for (int i = 0; i < position; i++) {
629 | Object listItem = getListItem(i);
630 | if (!(listItem instanceof ParentWrapper)) {
631 | expandedCount++;
632 | }
633 | }
634 | return expandedCount;
635 | }
636 |
637 | // endregion
638 |
639 | // region Data Manipulation
640 |
641 | /**
642 | * Notify any registered observers that the ParentListItem reflected at {@code parentPosition}
643 | * has been newly inserted. The ParentListItem previously at {@code parentPosition} is now at
644 | * position {@code parentPosition + 1}.
645 | *
646 | * This is a structural change event. Representations of other existing items in the
647 | * data set are still considered up to date and will not be rebound, though their
648 | * positions may be altered.
649 | *
650 | * @param parentPosition Position of the newly inserted ParentListItem in the data set, relative
651 | * to list of ParentListItems only.
652 | *
653 | * @see #notifyParentItemRangeInserted(int, int)
654 | */
655 | public void notifyParentItemInserted(int parentPosition) {
656 | ParentListItem parentListItem = mParentItemList.get(parentPosition);
657 |
658 | int wrapperIndex;
659 | if (parentPosition < mParentItemList.size() - 1) {
660 | wrapperIndex = getParentWrapperIndex(parentPosition);
661 | } else {
662 | wrapperIndex = mItemList.size();
663 | }
664 |
665 | int sizeChanged = addParentWrapper(wrapperIndex, parentListItem);
666 | notifyItemRangeInserted(wrapperIndex, sizeChanged);
667 | }
668 |
669 | /**
670 | * Notify any registered observers that the currently reflected {@code itemCount}
671 | * ParentListItems starting at {@code parentPositionStart} have been newly inserted.
672 | * The ParentListItems previously located at {@code parentPositionStart} and beyond
673 | * can now be found starting at position {@code parentPositionStart + itemCount}.
674 | *
675 | * This is a structural change event. Representations of other existing items in the
676 | * data set are still considered up to date and will not be rebound, though their positions
677 | * may be altered.
678 | *
679 | * @param parentPositionStart Position of the first ParentListItem that was inserted, relative
680 | * to list of ParentListItems only.
681 | * @param itemCount Number of items inserted
682 | *
683 | * @see #notifyParentItemInserted(int)
684 | */
685 | public void notifyParentItemRangeInserted(int parentPositionStart, int itemCount) {
686 | int initialWrapperIndex;
687 | if (parentPositionStart < mParentItemList.size() - itemCount) {
688 | initialWrapperIndex = getParentWrapperIndex(parentPositionStart);
689 | } else {
690 | initialWrapperIndex = mItemList.size();
691 | }
692 |
693 | int sizeChanged = 0;
694 | int wrapperIndex = initialWrapperIndex;
695 | int changed;
696 | int parentPositionEnd = parentPositionStart + itemCount;
697 | for (int i = parentPositionStart; i < parentPositionEnd; i++) {
698 | ParentListItem parentListItem = mParentItemList.get(i);
699 | changed = addParentWrapper(wrapperIndex, parentListItem);
700 | wrapperIndex += changed;
701 | sizeChanged += changed;
702 | }
703 |
704 | notifyItemRangeInserted(initialWrapperIndex, sizeChanged);
705 | }
706 |
707 | private int addParentWrapper(int wrapperIndex, ParentListItem parentListItem) {
708 | int sizeChanged = 1;
709 | ParentWrapper parentWrapper = new ParentWrapper(parentListItem);
710 | mItemList.add(wrapperIndex, parentWrapper);
711 | if (parentWrapper.isInitiallyExpanded()) {
712 | parentWrapper.setExpanded(true);
713 | List> childItemList = parentWrapper.getChildItemList();
714 | mItemList.addAll(wrapperIndex + sizeChanged, childItemList);
715 | sizeChanged += childItemList.size();
716 | }
717 | return sizeChanged;
718 | }
719 |
720 | /**
721 | * Notify any registered observers that the ParentListItem previously located at {@code parentPosition}
722 | * has been removed from the data set. The ParentListItems previously located at and after
723 | * {@code parentPosition} may now be found at {@code oldPosition - 1}.
724 | *
725 | * This is a structural change event. Representations of other existing items in the
726 | * data set are still considered up to date and will not be rebound, though their positions
727 | * may be altered.
728 | *
729 | * @param parentPosition Position of the ParentListItem that has now been removed, relative
730 | * to list of ParentListItems only.
731 | */
732 | public void notifyParentItemRemoved(int parentPosition) {
733 | int wrapperIndex = getParentWrapperIndex(parentPosition);
734 | int sizeChanged = removeParentWrapper(wrapperIndex);
735 |
736 | notifyItemRangeRemoved(wrapperIndex, sizeChanged);
737 | }
738 |
739 | /**
740 | * Notify any registered observers that the {@code itemCount} ParentListItems previously located
741 | * at {@code parentPositionStart} have been removed from the data set. The ParentListItems
742 | * previously located at and after {@code parentPositionStart + itemCount} may now be found at
743 | * {@code oldPosition - itemCount}.
744 | *
745 | * This is a structural change event. Representations of other existing items in the
746 | * data set are still considered up to date and will not be rebound, though their positions
747 | * may be altered.
748 | *
749 | * @param parentPositionStart The previous position of the first ParentListItem that was
750 | * removed, relative to list of ParentListItems only.
751 | * @param itemCount Number of ParentListItems removed from the data set
752 | */
753 | public void notifyParentItemRangeRemoved(int parentPositionStart, int itemCount) {
754 | int sizeChanged = 0;
755 | int wrapperIndex = getParentWrapperIndex(parentPositionStart);
756 | for (int i = 0; i < itemCount; i++) {
757 | sizeChanged += removeParentWrapper(wrapperIndex);
758 | }
759 |
760 | notifyItemRangeRemoved(wrapperIndex, sizeChanged);
761 | }
762 |
763 | private int removeParentWrapper(int parentWrapperIndex) {
764 | int sizeChanged = 1;
765 | ParentWrapper parentWrapper = (ParentWrapper) mItemList.remove(parentWrapperIndex);
766 | if (parentWrapper.isExpanded()) {
767 | int childListSize = parentWrapper.getChildItemList().size();
768 | for (int i = 0; i < childListSize; i++) {
769 | mItemList.remove(parentWrapperIndex);
770 | sizeChanged++;
771 | }
772 | }
773 | return sizeChanged;
774 | }
775 |
776 | /**
777 | * Notify any registered observers that the ParentListItem at {@code parentPosition} has changed.
778 | * This will also trigger an item changed for children of the ParentList specified.
779 | *
780 | * This is an item change event, not a structural change event. It indicates that any
781 | * reflection of the data at {@code parentPosition} is out of date and should be updated.
782 | * The ParentListItem at {@code parentPosition} retains the same identity. This means
783 | * the number of children must stay the same.
784 | *
785 | * @param parentPosition Position of the item that has changed
786 | */
787 | public void notifyParentItemChanged(int parentPosition) {
788 | ParentListItem parentListItem = mParentItemList.get(parentPosition);
789 | int wrapperIndex = getParentWrapperIndex(parentPosition);
790 | int sizeChanged = changeParentWrapper(wrapperIndex, parentListItem);
791 |
792 | notifyItemRangeChanged(wrapperIndex, sizeChanged);
793 | }
794 |
795 | /**
796 | * Notify any registered observers that the {@code itemCount} ParentListItems starting
797 | * at {@code parentPositionStart} have changed. This will also trigger an item changed
798 | * for children of the ParentList specified.
799 | *
800 | * This is an item change event, not a structural change event. It indicates that any
801 | * reflection of the data in the given position range is out of date and should be updated.
802 | * The ParentListItems in the given range retain the same identity. This means
803 | * the number of children must stay the same.
804 | *
805 | * @param parentPositionStart Position of the item that has changed
806 | * @param itemCount Number of ParentListItems changed in the dataset
807 | */
808 | public void notifyParentItemRangeChanged(int parentPositionStart, int itemCount) {
809 | int initialWrapperIndex = getParentWrapperIndex(parentPositionStart);
810 |
811 | int wrapperIndex = initialWrapperIndex;
812 | int sizeChanged = 0;
813 | int changed;
814 | ParentListItem parentListItem;
815 | for (int j = 0; j < itemCount; j++) {
816 | parentListItem = mParentItemList.get(parentPositionStart);
817 | changed = changeParentWrapper(wrapperIndex, parentListItem);
818 | sizeChanged += changed;
819 | wrapperIndex += changed;
820 | parentPositionStart++;
821 | }
822 | notifyItemRangeChanged(initialWrapperIndex, sizeChanged);
823 | }
824 |
825 | private int changeParentWrapper(int wrapperIndex, ParentListItem parentListItem) {
826 | ParentWrapper parentWrapper = (ParentWrapper) mItemList.get(wrapperIndex);
827 | parentWrapper.setParentListItem(parentListItem);
828 | int sizeChanged = 1;
829 | if (parentWrapper.isExpanded()) {
830 | List> childItems = parentWrapper.getChildItemList();
831 | int childListSize = childItems.size();
832 | Object child;
833 | for (int i = 0; i < childListSize; i++) {
834 | child = childItems.get(i);
835 | mItemList.set(wrapperIndex + i + 1, child);
836 | sizeChanged++;
837 | }
838 | }
839 |
840 | return sizeChanged;
841 |
842 | }
843 |
844 | /**
845 | * Notify any registered observers that the ParentListItem and it's child list items reflected at
846 | * {@code fromParentPosition} has been moved to {@code toParentPosition}.
847 | *
848 | *
This is a structural change event. Representations of other existing items in the
849 | * data set are still considered up to date and will not be rebound, though their
850 | * positions may be altered.
851 | *
852 | * @param fromParentPosition Previous position of the ParentListItem, relative to list of
853 | * ParentListItems only.
854 | * @param toParentPosition New position of the ParentListItem, relative to list of
855 | * ParentListItems only.
856 | */
857 | public void notifyParentItemMoved(int fromParentPosition, int toParentPosition) {
858 |
859 | int fromWrapperIndex = getParentWrapperIndex(fromParentPosition);
860 | ParentWrapper fromParentWrapper = (ParentWrapper) mItemList.get(fromWrapperIndex);
861 |
862 | // If the parent is collapsed we can take advantage of notifyItemMoved otherwise
863 | // we are forced to do a "manual" move by removing and then adding the parent + children
864 | // (no notifyItemRangeMovedAvailable)
865 | boolean isCollapsed = !fromParentWrapper.isExpanded();
866 | boolean isExpandedNoChildren = !isCollapsed && (fromParentWrapper.getChildItemList().size() == 0);
867 | if (isCollapsed || isExpandedNoChildren) {
868 | int toWrapperIndex = getParentWrapperIndex(toParentPosition);
869 | ParentWrapper toParentWrapper = (ParentWrapper) mItemList.get(toWrapperIndex);
870 | mItemList.remove(fromWrapperIndex);
871 | int childOffset = 0;
872 | if (toParentWrapper.isExpanded()) {
873 | childOffset = toParentWrapper.getChildItemList().size();
874 | }
875 | mItemList.add(toWrapperIndex + childOffset, fromParentWrapper);
876 |
877 | notifyItemMoved(fromWrapperIndex, toWrapperIndex + childOffset);
878 | } else {
879 | // Remove the parent and children
880 | int sizeChanged = 0;
881 | int childListSize = fromParentWrapper.getChildItemList().size();
882 | for (int i = 0; i < childListSize + 1; i++) {
883 | mItemList.remove(fromWrapperIndex);
884 | sizeChanged++;
885 | }
886 | notifyItemRangeRemoved(fromWrapperIndex, sizeChanged);
887 |
888 |
889 | // Add the parent and children at new position
890 | int toWrapperIndex = getParentWrapperIndex(toParentPosition);
891 | int childOffset = 0;
892 | if (toWrapperIndex != -1) {
893 | ParentWrapper toParentWrapper = (ParentWrapper) mItemList.get(toWrapperIndex);
894 | if (toParentWrapper.isExpanded()) {
895 | childOffset = toParentWrapper.getChildItemList().size();
896 | }
897 | } else {
898 | toWrapperIndex = mItemList.size();
899 | }
900 | mItemList.add(toWrapperIndex + childOffset, fromParentWrapper);
901 | List> childItemList = fromParentWrapper.getChildItemList();
902 | sizeChanged = childItemList.size() + 1;
903 | mItemList.addAll(toWrapperIndex + childOffset + 1, childItemList);
904 | notifyItemRangeInserted(toWrapperIndex + childOffset, sizeChanged);
905 | }
906 | }
907 |
908 | /**
909 | * Notify any registered observers that the ParentListItem reflected at {@code parentPosition}
910 | * has a child list item that has been newly inserted at {@code childPosition}.
911 | * The child list item previously at {@code childPosition} is now at
912 | * position {@code childPosition + 1}.
913 | *
914 | * This is a structural change event. Representations of other existing items in the
915 | * data set are still considered up to date and will not be rebound, though their
916 | * positions may be altered.
917 | *
918 | * @param parentPosition Position of the ParentListItem which has been added a child, relative
919 | * to list of ParentListItems only.
920 | * @param childPosition Position of the child object that has been inserted, relative to children
921 | * of the ParentListItem specified by {@code parentPosition} only.
922 | *
923 | */
924 | public void notifyChildItemInserted(int parentPosition, int childPosition) {
925 | int parentWrapperIndex = getParentWrapperIndex(parentPosition);
926 | ParentWrapper parentWrapper = (ParentWrapper) mItemList.get(parentWrapperIndex);
927 |
928 | if (parentWrapper.isExpanded()) {
929 | ParentListItem parentListItem = mParentItemList.get(parentPosition);
930 | Object child = parentListItem.getChildItemList().get(childPosition);
931 | mItemList.add(parentWrapperIndex + childPosition + 1, child);
932 | notifyItemInserted(parentWrapperIndex + childPosition + 1);
933 | }
934 | }
935 |
936 | /**
937 | * Notify any registered observers that the ParentListItem reflected at {@code parentPosition}
938 | * has {@code itemCount} child list items that have been newly inserted at {@code childPositionStart}.
939 | * The child list item previously at {@code childPositionStart} and beyond are now at
940 | * position {@code childPositionStart + itemCount}.
941 | *
942 | * This is a structural change event. Representations of other existing items in the
943 | * data set are still considered up to date and will not be rebound, though their
944 | * positions may be altered.
945 | *
946 | * @param parentPosition Position of the ParentListItem which has been added a child, relative
947 | * to list of ParentListItems only.
948 | * @param childPositionStart Position of the first child object that has been inserted,
949 | * relative to children of the ParentListItem specified by
950 | * {@code parentPosition} only.
951 | * @param itemCount number of children inserted
952 | *
953 | */
954 | public void notifyChildItemRangeInserted(int parentPosition, int childPositionStart, int itemCount) {
955 | int parentWrapperIndex = getParentWrapperIndex(parentPosition);
956 | ParentWrapper parentWrapper = (ParentWrapper) mItemList.get(parentWrapperIndex);
957 |
958 | if (parentWrapper.isExpanded()) {
959 | ParentListItem parentListItem = mParentItemList.get(parentPosition);
960 | List> childList = parentListItem.getChildItemList();
961 | Object child;
962 | for (int i = 0; i < itemCount; i++) {
963 | child = childList.get(childPositionStart + i);
964 | mItemList.add(parentWrapperIndex + childPositionStart + i + 1, child);
965 | }
966 | notifyItemRangeInserted(parentWrapperIndex + childPositionStart + 1, itemCount);
967 | }
968 | }
969 |
970 | /**
971 | * Notify any registered observers that the ParentListItem located at {@code parentPosition}
972 | * has a child list item that has been removed from the data set, previously located at {@code childPosition}.
973 | * The child list item previously located at and after {@code childPosition} may
974 | * now be found at {@code childPosition - 1}.
975 | *
976 | * This is a structural change event. Representations of other existing items in the
977 | * data set are still considered up to date and will not be rebound, though their positions
978 | * may be altered.
979 | *
980 | * @param parentPosition Position of the ParentListItem which has a child removed from, relative
981 | * to list of ParentListItems only.
982 | * @param childPosition Position of the child object that has been removed, relative to children
983 | * of the ParentListItem specified by {@code parentPosition} only.
984 | */
985 | public void notifyChildItemRemoved(int parentPosition, int childPosition) {
986 | int parentWrapperIndex = getParentWrapperIndex(parentPosition);
987 | ParentWrapper parentWrapper = (ParentWrapper) mItemList.get(parentWrapperIndex);
988 |
989 | if (parentWrapper.isExpanded()) {
990 | mItemList.remove(parentWrapperIndex + childPosition + 1);
991 | notifyItemRemoved(parentWrapperIndex + childPosition + 1);
992 | }
993 | }
994 |
995 | /**
996 | * Notify any registered observers that the ParentListItem located at {@code parentPosition}
997 | * has {@code itemCount} child list items that have been removed from the data set, previously
998 | * located at {@code childPositionStart} onwards. The child list item previously located at and
999 | * after {@code childPositionStart} may now be found at {@code childPositionStart - itemCount}.
1000 | *
1001 | * This is a structural change event. Representations of other existing items in the
1002 | * data set are still considered up to date and will not be rebound, though their positions
1003 | * may be altered.
1004 | *
1005 | * @param parentPosition Position of the ParentListItem which has a child removed from, relative
1006 | * to list of ParentListItems only.
1007 | * @param childPositionStart Position of the first child object that has been removed, relative
1008 | * to children of the ParentListItem specified by
1009 | * {@code parentPosition} only.
1010 | * @param itemCount number of children removed
1011 | */
1012 | public void notifyChildItemRangeRemoved(int parentPosition, int childPositionStart, int itemCount) {
1013 | int parentWrapperIndex = getParentWrapperIndex(parentPosition);
1014 | ParentWrapper parentWrapper = (ParentWrapper) mItemList.get(parentWrapperIndex);
1015 |
1016 | if (parentWrapper.isExpanded()) {
1017 | for (int i = 0; i < itemCount; i++) {
1018 | mItemList.remove(parentWrapperIndex + childPositionStart + 1);
1019 | }
1020 | notifyItemRangeRemoved(parentWrapperIndex + childPositionStart + 1, itemCount);
1021 | }
1022 | }
1023 |
1024 | /**
1025 | * Notify any registered observers that the ParentListItem at {@code parentPosition} has
1026 | * a child located at {@code childPosition} that has changed.
1027 | *
1028 | * This is an item change event, not a structural change event. It indicates that any
1029 | * reflection of the data at {@code childPosition} is out of date and should be updated.
1030 | * The ParentListItem at {@code childPosition} retains the same identity.
1031 | *
1032 | * @param parentPosition Position of the ParentListItem who has a child that has changed
1033 | * @param childPosition Position of the child that has changed
1034 | */
1035 | public void notifyChildItemChanged(int parentPosition, int childPosition) {
1036 | ParentListItem parentListItem = mParentItemList.get(parentPosition);
1037 | int parentWrapperIndex = getParentWrapperIndex(parentPosition);
1038 | ParentWrapper parentWrapper = (ParentWrapper) mItemList.get(parentWrapperIndex);
1039 | parentWrapper.setParentListItem(parentListItem);
1040 | if (parentWrapper.isExpanded()) {
1041 | int listChildPosition = parentWrapperIndex + childPosition + 1;
1042 | Object child = parentWrapper.getChildItemList().get(childPosition);
1043 | mItemList.set(listChildPosition, child);
1044 | notifyItemChanged(listChildPosition);
1045 | }
1046 | }
1047 |
1048 | /**
1049 | * Notify any registered observers that the ParentListItem at {@code parentPosition} has
1050 | * {@code itemCount} child Objects starting at {@code childPositionStart} that have changed.
1051 | *
1052 | * This is an item change event, not a structural change event. It indicates that any
1053 | * The ParentListItem at {@code childPositionStart} retains the same identity.
1054 | * reflection of the set of {@code itemCount} child objects starting at {@code childPositionStart}
1055 | * are out of date and should be updated.
1056 | *
1057 | * @param parentPosition Position of the ParentListItem who has a child that has changed
1058 | * @param childPositionStart Position of the first child object that has changed
1059 | * @param itemCount number of child objects changed
1060 | */
1061 | public void notifyChildItemRangeChanged(int parentPosition, int childPositionStart, int itemCount) {
1062 | ParentListItem parentListItem = mParentItemList.get(parentPosition);
1063 | int parentWrapperIndex = getParentWrapperIndex(parentPosition);
1064 | ParentWrapper parentWrapper = (ParentWrapper) mItemList.get(parentWrapperIndex);
1065 | parentWrapper.setParentListItem(parentListItem);
1066 | if (parentWrapper.isExpanded()) {
1067 | int listChildPosition = parentWrapperIndex + childPositionStart + 1;
1068 | for (int i = 0; i < itemCount; i++) {
1069 | Object child = parentWrapper.getChildItemList().get(childPositionStart + i);
1070 | mItemList.set(listChildPosition + i, child);
1071 |
1072 | }
1073 | notifyItemRangeChanged(listChildPosition, itemCount);
1074 | }
1075 | }
1076 |
1077 | /**
1078 | * Notify any registered observers that the child list item contained within the ParentListItem
1079 | * at {@code parentPosition} has moved from {@code fromChildPosition} to {@code toChildPosition}.
1080 | *
1081 | *
This is a structural change event. Representations of other existing items in the
1082 | * data set are still considered up to date and will not be rebound, though their
1083 | * positions may be altered.
1084 | *
1085 | * @param parentPosition Position of the ParentListItem who has a child that has moved
1086 | * @param fromChildPosition Previous position of the child list item
1087 | * @param toChildPosition New position of the child list item
1088 | */
1089 | public void notifyChildItemMoved(int parentPosition, int fromChildPosition, int toChildPosition) {
1090 | ParentListItem parentListItem = mParentItemList.get(parentPosition);
1091 | int parentWrapperIndex = getParentWrapperIndex(parentPosition);
1092 | ParentWrapper parentWrapper = (ParentWrapper) mItemList.get(parentWrapperIndex);
1093 | parentWrapper.setParentListItem(parentListItem);
1094 | if (parentWrapper.isExpanded()) {
1095 | Object fromChild = mItemList.remove(parentWrapperIndex + 1 + fromChildPosition);
1096 | mItemList.add(parentWrapperIndex + 1 + toChildPosition, fromChild);
1097 | notifyItemMoved(parentWrapperIndex + 1 + fromChildPosition, parentWrapperIndex + 1 + toChildPosition);
1098 | }
1099 | }
1100 |
1101 |
1102 |
1103 | // endregion
1104 |
1105 | /**
1106 | * Generates a HashMap used to store expanded state for items in the list
1107 | * on configuration change or whenever onResume is called.
1108 | *
1109 | * @return A HashMap containing the expanded state of all parent list items
1110 | */
1111 | private HashMap generateExpandedStateMap() {
1112 | HashMap parentListItemHashMap = new HashMap<>();
1113 | int childCount = 0;
1114 |
1115 | Object listItem;
1116 | ParentWrapper parentWrapper;
1117 | int listItemCount = mItemList.size();
1118 | for (int i = 0; i < listItemCount; i++) {
1119 | if (mItemList.get(i) != null) {
1120 | listItem = getListItem(i);
1121 | if (listItem instanceof ParentWrapper) {
1122 | parentWrapper = (ParentWrapper) listItem;
1123 | parentListItemHashMap.put(i - childCount, parentWrapper.isExpanded());
1124 | } else {
1125 | childCount++;
1126 | }
1127 | }
1128 | }
1129 |
1130 | return parentListItemHashMap;
1131 | }
1132 |
1133 | /**
1134 | * Gets the index of a ParentWrapper within the helper item list based on
1135 | * the index of the ParentWrapper.
1136 | *
1137 | * @param parentIndex The index of the parent in the list of parent items
1138 | * @return The index of the parent in the list of all views in the adapter
1139 | */
1140 | private int getParentWrapperIndex(int parentIndex) {
1141 | int parentCount = 0;
1142 | int listItemCount = mItemList.size();
1143 | for (int i = 0; i < listItemCount; i++) {
1144 | if (mItemList.get(i) instanceof ParentWrapper) {
1145 | parentCount++;
1146 |
1147 | if (parentCount > parentIndex) {
1148 | return i;
1149 | }
1150 | }
1151 | }
1152 |
1153 | return -1;
1154 | }
1155 |
1156 | /**
1157 | * Gets the ParentWrapper for a specified ParentListItem from the list of
1158 | * parents.
1159 | *
1160 | * @param parentListItem A ParentListItem in the list of parents
1161 | * @return If the parent exists on the list, returns its ParentWrapper.
1162 | * Otherwise, returns null.
1163 | */
1164 | private ParentWrapper getParentWrapper(ParentListItem parentListItem) {
1165 | int listItemCount = mItemList.size();
1166 | for (int i = 0; i < listItemCount; i++) {
1167 | Object listItem = mItemList.get(i);
1168 | if (listItem instanceof ParentWrapper) {
1169 | if (((ParentWrapper) listItem).getParentListItem().equals(parentListItem)) {
1170 | return (ParentWrapper) listItem;
1171 | }
1172 | }
1173 | }
1174 |
1175 | return null;
1176 | }
1177 | }
1178 |
--------------------------------------------------------------------------------