Full Code of i-vishi/shopping-android-app for AI

master 1768ebf52bb9 cached
223 files
593.6 KB
159.0k tokens
1 requests
Download .txt
Showing preview only (660K chars total). Download the full file or copy to clipboard to get everything.
Repository: i-vishi/shopping-android-app
Branch: master
Commit: 1768ebf52bb9
Files: 223
Total size: 593.6 KB

Directory structure:
gitextract_6suav1uc/

├── .gitignore
├── .idea/
│   ├── .gitignore
│   ├── .name
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── compiler.xml
│   ├── gradle.xml
│   ├── jarRepositories.xml
│   ├── misc.xml
│   ├── render.experimental.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── vishalgaur/
│       │               └── shoppingapp/
│       │                   ├── AppDatabaseTest.kt
│       │                   ├── ClickClickableSpan.kt
│       │                   ├── ExampleInstrumentedTest.kt
│       │                   ├── LiveDataTestUtil.kt
│       │                   ├── MainCoroutineRule.kt
│       │                   ├── RecyclerViewMatcherUtils.kt
│       │                   ├── data/
│       │                   │   └── source/
│       │                   │       ├── FakeAuthRepository.kt
│       │                   │       ├── FakeProductsDataSource.kt
│       │                   │       ├── FakeProductsRepository.kt
│       │                   │       ├── FakeUserDataSource.kt
│       │                   │       └── repository/
│       │                   │           ├── AuthRepositoryTest.kt
│       │                   │           └── ProductsRepositoryTest.kt
│       │                   ├── ui/
│       │                   │   ├── home/
│       │                   │   │   ├── HomeFragmentTest.kt
│       │                   │   │   └── ProductDetailsFragmentTest.kt
│       │                   │   └── loginSignup/
│       │                   │       ├── LoginFragmentTest.kt
│       │                   │       └── SignupFragmentTest.kt
│       │                   └── viewModels/
│       │                       ├── AddEditAddressViewModelTest.kt
│       │                       ├── AddEditProductViewModelTest.kt
│       │                       ├── AuthViewModelTest.kt
│       │                       ├── HomeViewModelTest.kt
│       │                       ├── OrderViewModelTest.kt
│       │                       └── ProductViewModelTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── vishalgaur/
│       │   │           └── shoppingapp/
│       │   │               ├── ServiceLocator.kt
│       │   │               ├── ShoppingApplication.kt
│       │   │               ├── Utils.kt
│       │   │               ├── data/
│       │   │               │   ├── Product.kt
│       │   │               │   ├── Result.kt
│       │   │               │   ├── ShoppingAppSessionManager.kt
│       │   │               │   ├── UserData.kt
│       │   │               │   ├── source/
│       │   │               │   │   ├── ProductDataSource.kt
│       │   │               │   │   ├── UserDataSource.kt
│       │   │               │   │   ├── local/
│       │   │               │   │   │   ├── ProductsDao.kt
│       │   │               │   │   │   ├── ProductsLocalDataSource.kt
│       │   │               │   │   │   ├── ShoppingAppDatabase.kt
│       │   │               │   │   │   ├── UserDao.kt
│       │   │               │   │   │   └── UserLocalDataSource.kt
│       │   │               │   │   ├── remote/
│       │   │               │   │   │   ├── AuthRemoteDataSource.kt
│       │   │               │   │   │   └── ProductsRemoteDataSource.kt
│       │   │               │   │   └── repository/
│       │   │               │   │       ├── AuthRepoInterface.kt
│       │   │               │   │       ├── AuthRepository.kt
│       │   │               │   │       ├── ProductsRepoInterface.kt
│       │   │               │   │       └── ProductsRepository.kt
│       │   │               │   └── utils/
│       │   │               │       ├── DateTypeConvertors.kt
│       │   │               │       ├── EmailMobileData.kt
│       │   │               │       ├── ListTypeConverter.kt
│       │   │               │       ├── ObjectListTypeConvertor.kt
│       │   │               │       ├── ProductUtils.kt
│       │   │               │       └── Utils.kt
│       │   │               ├── ui/
│       │   │               │   ├── LaunchActivity.kt
│       │   │               │   ├── RecyclerViewPaddingItemDecoration.kt
│       │   │               │   ├── UiUtils.kt
│       │   │               │   ├── home/
│       │   │               │   │   ├── AccountFragment.kt
│       │   │               │   │   ├── AddEditAddressFragment.kt
│       │   │               │   │   ├── AddEditProductFragment.kt
│       │   │               │   │   ├── AddProductImagesAdapter.kt
│       │   │               │   │   ├── AddressAdapter.kt
│       │   │               │   │   ├── AddressFragment.kt
│       │   │               │   │   ├── CartFragment.kt
│       │   │               │   │   ├── CartItemAdapter.kt
│       │   │               │   │   ├── FavoritesFragment.kt
│       │   │               │   │   ├── HomeFragment.kt
│       │   │               │   │   ├── LikedProductAdapter.kt
│       │   │               │   │   ├── MainActivity.kt
│       │   │               │   │   ├── OrderDetailsFragment.kt
│       │   │               │   │   ├── OrderProductsAdapter.kt
│       │   │               │   │   ├── OrderSuccessFragment.kt
│       │   │               │   │   ├── OrdersAdapter.kt
│       │   │               │   │   ├── OrdersFragment.kt
│       │   │               │   │   ├── PayByAdapter.kt
│       │   │               │   │   ├── ProductAdapter.kt
│       │   │               │   │   ├── ProductDetailsFragment.kt
│       │   │               │   │   ├── ProductImagesAdapter.kt
│       │   │               │   │   ├── ProfileFragment.kt
│       │   │               │   │   ├── SelectAddressFragment.kt
│       │   │               │   │   └── SelectPaymentFragment.kt
│       │   │               │   └── loginSignup/
│       │   │               │       ├── LoginFragment.kt
│       │   │               │       ├── LoginSignupActivity.kt
│       │   │               │       ├── LoginSignupBaseFragment.kt
│       │   │               │       ├── OtpActivity.kt
│       │   │               │       └── SignupFragment.kt
│       │   │               └── viewModels/
│       │   │                   ├── AddEditAddressViewModel.kt
│       │   │                   ├── AddEditProductViewModel.kt
│       │   │                   ├── AuthViewModel.kt
│       │   │                   ├── HomeViewModel.kt
│       │   │                   ├── OrderViewModel.kt
│       │   │                   ├── OtpViewModel.kt
│       │   │                   └── ProductViewModel.kt
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── address_account_drawable.xml
│       │       │   ├── avatar_background.xml
│       │       │   ├── bottom_nav_selector.xml
│       │       │   ├── btn_gradient.xml
│       │       │   ├── card_item_selector.xml
│       │       │   ├── color_radio_normal.xml
│       │       │   ├── color_radio_selected.xml
│       │       │   ├── color_radio_selector.xml
│       │       │   ├── dotted_line_drawable.xml
│       │       │   ├── heart_icon_drawable.xml
│       │       │   ├── ic_add_24.xml
│       │       │   ├── ic_add_48.xml
│       │       │   ├── ic_add_shopping_cart_24.xml
│       │       │   ├── ic_baseline_person_24.xml
│       │       │   ├── ic_baseline_shopping_cart_24.xml
│       │       │   ├── ic_cancel_24.xml
│       │       │   ├── ic_chevron_left_48.xml
│       │       │   ├── ic_delete_24.xml
│       │       │   ├── ic_edit_24.xml
│       │       │   ├── ic_favorite_filled_24.xml
│       │       │   ├── ic_favorite_outlined_24.xml
│       │       │   ├── ic_filled_check_circle_24.xml
│       │       │   ├── ic_filled_library_books_24.xml
│       │       │   ├── ic_filled_location_on_24.xml
│       │       │   ├── ic_filled_logout_24.xml
│       │       │   ├── ic_filled_person_24.xml
│       │       │   ├── ic_filter_alt_24.xml
│       │       │   ├── ic_launcher_background.xml
│       │       │   ├── ic_menu_24.xml
│       │       │   ├── ic_outline_arrow_back_24.xml
│       │       │   ├── ic_outline_email_24.xml
│       │       │   ├── ic_outline_library_books_24.xml
│       │       │   ├── ic_outline_phone_android_24.xml
│       │       │   ├── ic_outlined_home_24.xml
│       │       │   ├── ic_outlined_person_24.xml
│       │       │   ├── ic_outlined_shopping_cart_24.xml
│       │       │   ├── ic_remove_24.xml
│       │       │   ├── ic_remove_shopping_cart_24.xml
│       │       │   ├── ic_search_24.xml
│       │       │   ├── layout_background_rounded_corners.xml
│       │       │   ├── liked_heart_drawable.xml
│       │       │   ├── login_bg_img.xml
│       │       │   ├── orders_account_drawable.xml
│       │       │   ├── person_account_drawable.xml
│       │       │   ├── radio_normal.xml
│       │       │   ├── radio_selected.xml
│       │       │   ├── radio_selector.xml
│       │       │   ├── round_button.xml
│       │       │   ├── round_outline_rect.xml
│       │       │   ├── signout_account_drawable.xml
│       │       │   └── sl_favourite_24dp.xml
│       │       ├── drawable-v24/
│       │       │   └── ic_launcher_foreground.xml
│       │       ├── font/
│       │       │   ├── nunito_sans.xml
│       │       │   └── nunito_sans_extrabold.xml
│       │       ├── layout/
│       │       │   ├── activity_launch.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── activity_otp.xml
│       │       │   ├── activity_signup.xml
│       │       │   ├── add_images_item.xml
│       │       │   ├── cart_list_item.xml
│       │       │   ├── country_list_item.xml
│       │       │   ├── fragment_account.xml
│       │       │   ├── fragment_add_edit_address.xml
│       │       │   ├── fragment_add_edit_product.xml
│       │       │   ├── fragment_address.xml
│       │       │   ├── fragment_cart.xml
│       │       │   ├── fragment_favorites.xml
│       │       │   ├── fragment_home.xml
│       │       │   ├── fragment_login.xml
│       │       │   ├── fragment_order_details.xml
│       │       │   ├── fragment_order_success.xml
│       │       │   ├── fragment_orders.xml
│       │       │   ├── fragment_product_details.xml
│       │       │   ├── fragment_profile.xml
│       │       │   ├── fragment_select_address.xml
│       │       │   ├── fragment_select_payment.xml
│       │       │   ├── fragment_signup.xml
│       │       │   ├── images_item.xml
│       │       │   ├── layout_address_card.xml
│       │       │   ├── layout_circular_loader.xml
│       │       │   ├── layout_home_ad.xml
│       │       │   ├── layout_home_top_app_bar.xml
│       │       │   ├── layout_list_item.xml
│       │       │   ├── layout_loader_card.xml
│       │       │   ├── layout_no_icon_app_bar.xml
│       │       │   ├── layout_order_summary_card.xml
│       │       │   ├── layout_price_card.xml
│       │       │   ├── layout_shipping_card.xml
│       │       │   ├── layout_top_bar.xml
│       │       │   └── products_list_item.xml
│       │       ├── menu/
│       │       │   ├── app_bar_menu.xml
│       │       │   ├── bottom_navigation_menu.xml
│       │       │   ├── home_app_bar_menu.xml
│       │       │   ├── menu_main.xml
│       │       │   └── menu_with_add_only.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       ├── navigation/
│       │       │   ├── home_nav_graph.xml
│       │       │   └── signup_nav_graph.xml
│       │       ├── values/
│       │       │   ├── attrs.xml
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── font_certs.xml
│       │       │   ├── preloaded_fonts.xml
│       │       │   ├── shapes.xml
│       │       │   ├── strings.xml
│       │       │   ├── styles.xml
│       │       │   ├── themes.xml
│       │       │   └── type.xml
│       │       └── values-night/
│       │           └── themes.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── vishalgaur/
│                       └── shoppingapp/
│                           └── UtilsTest.kt
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties


================================================
FILE: .idea/.gitignore
================================================


================================================
FILE: .idea/.name
================================================
Shopping App

================================================
FILE: .idea/codeStyles/Project.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <code_scheme name="Project" version="173">
    <JetCodeStyleSettings>
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
    </JetCodeStyleSettings>
    <codeStyleSettings language="XML">
      <option name="FORCE_REARRANGE_MODE" value="1" />
      <indentOptions>
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
      </indentOptions>
      <arrangement>
        <rules>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:android</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>xmlns:.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:id</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*:name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>name</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>style</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>^$</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
                </AND>
              </match>
              <order>ANDROID_ATTRIBUTE_ORDER</order>
            </rule>
          </section>
          <section>
            <rule>
              <match>
                <AND>
                  <NAME>.*</NAME>
                  <XML_ATTRIBUTE />
                  <XML_NAMESPACE>.*</XML_NAMESPACE>
                </AND>
              </match>
              <order>BY_NAME</order>
            </rule>
          </section>
        </rules>
      </arrangement>
    </codeStyleSettings>
    <codeStyleSettings language="kotlin">
      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
      <indentOptions>
        <option name="USE_TAB_CHARACTER" value="true" />
      </indentOptions>
    </codeStyleSettings>
  </code_scheme>
</component>

================================================
FILE: .idea/codeStyles/codeStyleConfig.xml
================================================
<component name="ProjectCodeStyleConfiguration">
  <state>
    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
  </state>
</component>

================================================
FILE: .idea/compiler.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="CompilerConfiguration">
    <bytecodeTargetLevel target="11" />
  </component>
</project>

================================================
FILE: .idea/gradle.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="GradleMigrationSettings" migrationVersion="1" />
  <component name="GradleSettings">
    <option name="linkedExternalProjectsSettings">
      <GradleProjectSettings>
        <option name="testRunner" value="PLATFORM" />
        <option name="distributionType" value="DEFAULT_WRAPPED" />
        <option name="externalProjectPath" value="$PROJECT_DIR$" />
        <option name="gradleJvm" value="1.8" />
        <option name="modules">
          <set>
            <option value="$PROJECT_DIR$" />
            <option value="$PROJECT_DIR$/app" />
          </set>
        </option>
        <option name="resolveModulePerSourceSet" value="false" />
        <option name="useQualifiedModuleNames" value="true" />
      </GradleProjectSettings>
    </option>
  </component>
</project>

================================================
FILE: .idea/jarRepositories.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RemoteRepositoriesConfiguration">
    <remote-repository>
      <option name="id" value="central" />
      <option name="name" value="Maven Central repository" />
      <option name="url" value="https://repo1.maven.org/maven2" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="jboss.community" />
      <option name="name" value="JBoss Community repository" />
      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="BintrayJCenter" />
      <option name="name" value="BintrayJCenter" />
      <option name="url" value="https://jcenter.bintray.com/" />
    </remote-repository>
    <remote-repository>
      <option name="id" value="Google" />
      <option name="name" value="Google" />
      <option name="url" value="https://dl.google.com/dl/android/maven2/" />
    </remote-repository>
  </component>
</project>

================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
    <output url="file://$PROJECT_DIR$/build/classes" />
  </component>
  <component name="ProjectType">
    <option name="id" value="Android" />
  </component>
</project>

================================================
FILE: .idea/render.experimental.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RenderSettings">
    <option name="showDecorations" value="true" />
  </component>
</project>

================================================
FILE: .idea/runConfigurations.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="RunConfigurationProducerService">
    <option name="ignoredProducers">
      <set>
        <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
      </set>
    </option>
  </component>
</project>

================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
  </component>
</project>

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Vishal Gaur

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Shopping Android App
An e-commerce android application written in Kotlin where users can sell and buy products. 


## Overview
The application contains list of products such as shoes, slippers on which user can click to view its details and then, add them to cart. User can like and dislike the product as well. Also, User can sell products, if he/she signed up as a Seller.
Some other features are as following:
- Login / Signup with OTP Verification.
- Recyclerview with variable span size to show products.
- Search Bar and filtering
- Product detail screen with image carousel and custom Radio Buttons.
- Add/Edit Product for Sellers
- See all orders placed.
- Increase/Decrease quantity of product in cart.
- Place Order.
- Modify status of order for Seller.
- Add/Edit Address
- Tested using Espresso. Written unit, instrumentation and UI tests.

## Some Screenshots

|             Splash Screen            |             Application Home              |           Product Detail            |
| :----------------------------------: | :---------------------------------------: | :----------------------------------:|
| ![](snapshots/shopping-launcher.png) | ![](snapshots/shopping-home-customer.png) | ![](snapshots/shopping-product.png) |

|                 Signup              |                Login              |        OTP Verification         |
| :---------------------------------: | :-------------------------------: | :------------------------------:|
| ![](snapshots/shopping-sign-up.png) | ![](snapshots/shopping-login.png) | ![](snapshots/shopping-otp.png) |

|           Shopping Cart          |             Address Selection              |             Payment Method             |               Order Success               |
| :------------------------------: | :----------------------------------------: | :-------------------------------------:| :---------------------------------------: |
| ![](snapshots/shopping-cart.png) | ![](snapshots/shopping-select-address.png) | ![](snapshots/shopping-choose-pay.png) | ![](snapshots/shopping-order-success.png) |

|               Add Product               |             All Orders             |                Order Detail              |               Sign Out               |
| :-------------------------------------: | :--------------------------------: | :---------------------------------------:| :----------------------------------: |
| ![](snapshots/shopping-add-product.png) | ![](snapshots/shopping-orders.png) | ![](snapshots/shopping-order-detail.png) | ![](snapshots/shopping-sign-out.png) |

## Project Setup

### Clone and install

Clone this repository and import into Android Studio
```
git clone https://github.com/i-vishi/shopping-android-app.git
```

### Configuration
- The project requires Firebase. So follow the steps given [here (Add Firebase to Android Project)](https://firebase.google.com/docs/android/setup) to add firebase to your android project.
- Download the firebase config file `google-services.json`
- Move the config file to `(app)` module of the project.
- Also, add Cloud Firestore for storing users, products, orders and addresses data. Follow instructions [here (Add Cloud Firestore to your app)](https://firebase.google.com/docs/firestore/quickstart) to add Cloud Firestore to your app.
- There need to be two collections - `users` for Users data and `products` for Products Data.
- This project also, requires OTP based authentication. So, you just need to enable Phone Number sign-in in your firebase project. Follow instructions [here (Enable Phone Number sign-in)](https://firebase.google.com/docs/auth/android/phone-auth) to enable Phone Number sign-in. 
- Do not forget to enable app verification for your firebase project. Follow instructions [here (Enable app verification)](https://firebase.google.com/docs/auth/android/phone-auth#enable-app-verification) to enable app verification. Add both SHA-1 and SHA-256 fingerprints.

Tried everything but still not able to explore the app due to OTP errors? Don't worry, you can by-pass the OTP screen and explore the app.
- Go to `app/src/main/java/com/vishalgaur/shoppingapp/Utils.kt` file.
- Change the return value for function `shouldBypassOTPValidation()` to `true`.
- You are good to go now. Just run the app and explore.
- And take your time to setup the OTP verification. :wink:


## Star History

<a href="https://star-history.com/#i-vishi/shopping-android-app&Date">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=i-vishi/shopping-android-app&type=Date&theme=dark" />
   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=i-vishi/shopping-android-app&type=Date" />
   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=i-vishi/shopping-android-app&type=Date" />
 </picture>
</a>


## Built With
- Kotlin
- Firebase
- Room
- Material
- Glide


---

<p align="center"> Made with :blue_heart: by <a href="https://github.com/i-vishi">Vishal Gaur</a></p>


================================================
FILE: app/.gitignore
================================================
/build

/google-services.json


================================================
FILE: app/build.gradle
================================================
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'com.google.gms.google-services'
    id 'kotlin-parcelize'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.vishalgaur.shoppingapp"
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildFeatures {
        viewBinding true
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

    packagingOptions {
        exclude 'META-INF/AL2.0'
        exclude 'META-INF/LGPL2.1'
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

    // Source: https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.1'

    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'

    // RecyclerView - beta with ConcatAdapter (previously MergeAdapter)
    implementation "androidx.recyclerview:recyclerview:1.2.0"

    // Room
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    kapt "androidx.room:room-compiler:$room_version"

    // ViewModel and LiveData
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    implementation 'com.google.firebase:firebase-analytics'
    implementation 'com.google.firebase:firebase-auth-ktx'

    // Glide
    implementation "com.github.bumptech.glide:glide:$glide_version"

    //Firebase
    implementation platform('com.google.firebase:firebase-bom:27.0.0')
    implementation 'com.google.firebase:firebase-firestore-ktx'
    implementation 'com.google.firebase:firebase-storage-ktx'

    // Testing
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation "androidx.test.ext:junit-ktx:1.1.2"
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    // AndroidJUnitRunner and JUnit Rules
    androidTestImplementation 'androidx.test:runner:1.3.0'
    androidTestImplementation 'androidx.test:rules:1.3.0'

    // espresso for intents
    androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0'

    //espresso for recyclerview
    androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'

    // AndroidX Test - JVM testing
    testImplementation "androidx.test.ext:junit-ktx:1.1.2"
    testImplementation "androidx.arch.core:core-testing:2.1.0"
    testImplementation "androidx.test:core-ktx:1.3.0"

    testImplementation "org.robolectric:robolectric:4.3.1"

    androidTestImplementation "androidx.arch.core:core-testing:2.1.0"


    // testing coroutines
    androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3'

    //fragment testing
    debugImplementation "androidx.fragment:fragment-testing:1.3.3"

    //navigation testing
    androidTestImplementation "androidx.navigation:navigation-testing:2.3.5"
}

================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/AppDatabaseTest.kt
================================================
package com.vishalgaur.shoppingapp

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.local.ProductsDao
import com.vishalgaur.shoppingapp.data.source.local.ShoppingAppDatabase
import com.vishalgaur.shoppingapp.data.source.local.UserDao
import kotlinx.coroutines.runBlocking
import org.hamcrest.Matchers.*
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AppDatabaseTest {
	private val pro1 = Product(
		"pro-owner1-shoe-101",
		"Shoe Name 101",
		"owner1",
		"some description",
		"Shoes",
		250.0,
		300.0,
		listOf(5, 6, 7, 8),
		listOf("Red", "Blue"),
		listOf("http://image-ref-uri/shoe-101-01.jpg", "http://image-ref-uri/-shoe-101-02.jpg"),
		2.5
	)
	private val pro2 = Product(
		"pro-owner1-slipper-101",
		"Slipper Name 101",
		"owner1",
		"some description",
		"Slippers",
		50.0,
		80.0,
		listOf(6, 7, 8),
		listOf("Black", "Blue"),
		listOf(
			"http://image-ref-uri/-slipper-101-01.jpg",
			"http://image-ref-uri/-slipper-101-02.jpg"
		),
		4.0
	)

	private lateinit var userDao: UserDao
	private lateinit var productsDao: ProductsDao
	private lateinit var appDb: ShoppingAppDatabase

	@get:Rule
	var instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun createDb() {
		val context = InstrumentationRegistry.getInstrumentation().targetContext

		appDb =
			Room.inMemoryDatabaseBuilder(context, ShoppingAppDatabase::class.java)
				.allowMainThreadQueries()
				.build()

		userDao = appDb.userDao()
		productsDao = appDb.productsDao()
	}

	@After
	fun closeDb() {
		appDb.clearAllTables()
		appDb.close()
	}

	@Test
	fun insertAndGetUser() {
		val user = UserData(
			"sdjm43892yfh948ehod",
			"Vishal",
			"+919999988888",
			"vishal@somemail.com",
			"dh94328hd",
			ArrayList(),
			ArrayList(),
			ArrayList()
		)
		runBlocking {
			userDao.insert(user)
			val result = userDao.getById("sdjm43892yfh948ehod")
			assertThat(result?.userId, `is`(user.userId))
		}

	}

	@Test
	fun noData_returnsNull() {
		runBlocking {
			val result = userDao.getById("1232")
			assertThat(result, `is`(nullValue()))
		}
	}

	@Test
	fun insertClearUser_returnsNull() {
		val user = UserData(
			"sdjm43892yfh948ehod",
			"Vishal",
			"+919999988888",
			"vishal@somemail.com",
			"dh94328hd",
			emptyList(),
			emptyList(),
			emptyList()
		)
		runBlocking {
			userDao.insert(user)
			userDao.clear()
			val result = userDao.getById("sdjm43892yfh948ehod")
			assertThat(result, `is`(nullValue()))
		}
	}

	@Test
	fun insertAndGetProduct() {
		runBlocking {
			productsDao.insert(pro1)
			val result = productsDao.getProductById(pro1.productId)
			assertEquals(pro1, result)
		}
	}

	@Test
	fun insertClearProduct_returnsNull() = runBlocking {
		productsDao.insert(pro1)
		productsDao.deleteAllProducts()
		val result = productsDao.getAllProducts()
		assertEquals(0, result.size)
	}

	@Test
	fun deleteProductById() = runBlocking {
		productsDao.insert(pro2)
		productsDao.deleteProductById(pro2.productId)
		val result = productsDao.getProductById(pro2.productId)
		assertThat(result, `is`(nullValue()))
	}

	@Test
	fun noProducts_returnsEmptyList() = runBlocking {
		val result = productsDao.getAllProducts()
		assertThat(result.size, `is`(0))
	}

	@Test
	fun deleteAllProducts_returnsEmptyList() = runBlocking {
		productsDao.insert(pro2)
		productsDao.deleteAllProducts()
		val result = productsDao.getAllProducts()
		assertThat(result.size, `is`(0))
	}

	@Test
	fun getProductsByOwner_returnsData() = runBlocking {
		productsDao.insert(pro2)
		val result = productsDao.getProductsByOwnerId(pro2.owner)
		assertThat(result.size, `is`(1))
	}

	@Test
	fun insertMultipleProducts() = runBlocking {
		productsDao.insertListOfProducts(listOf(pro1, pro2))
		val result = productsDao.getAllProducts()
		assertThat(result.size, `is`(2))
	}

	@Test
	fun observeProducts_returnsLiveData() = runBlocking {
		val initialRes = productsDao.observeProducts()
		productsDao.insert(pro1)
		val newValue = productsDao.observeProducts().getOrAwaitValue()

		assertThat(initialRes.value, not(newValue))
		assertThat(initialRes.value, `is`(nullValue()))
		assertThat(newValue.size, `is`(1))
	}

	@Test
	fun observeProductsByOwner_returnsLiveData() = runBlocking {
		val initialRes = productsDao.observeProductsByOwner(pro1.owner)
		productsDao.insert(pro1)
		val newValue = productsDao.observeProductsByOwner(pro1.owner).getOrAwaitValue()

		assertThat(initialRes.value, not(newValue))
		assertThat(initialRes.value, `is`(nullValue()))
		assertThat(newValue.size, `is`(1))
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/ClickClickableSpan.kt
================================================
package com.vishalgaur.shoppingapp

import android.text.SpannableString
import android.text.style.ClickableSpan
import android.view.View
import android.widget.TextView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import org.hamcrest.Matcher
import org.hamcrest.Matchers


internal fun clickClickableSpan(textToClick: CharSequence): ViewAction {
	return object : ViewAction {

		override fun getConstraints(): Matcher<View> {
			return Matchers.instanceOf(TextView::class.java)
		}

		override fun getDescription(): String {
			return "clicking on a ClickableSpan"
		}

		override fun perform(uiController: UiController, view: View) {
			val textView = view as TextView
			val spannableString = textView.text as SpannableString

			if (spannableString.isEmpty()) {
				// TextView is empty, nothing to do
				throw NoMatchingViewException.Builder()
					.includeViewHierarchy(true)
					.withRootView(textView)
					.build()
			}

			// Get the links inside the TextView and check if we find textToClick
			val spans =
				spannableString.getSpans(0, spannableString.length, ClickableSpan::class.java)
			if (spans.isNotEmpty()) {
				var spanCandidate: ClickableSpan
				for (span: ClickableSpan in spans) {
					spanCandidate = span
					val start = spannableString.getSpanStart(spanCandidate)
					val end = spannableString.getSpanEnd(spanCandidate)
					val sequence = spannableString.subSequence(start, end)
					if (textToClick.toString() == sequence.toString()) {
						span.onClick(textView)
						return
					}
				}
			}

			// textToClick not found in TextView
			throw NoMatchingViewException.Builder()
				.includeViewHierarchy(true)
				.withRootView(textView)
				.build()

		}
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/ExampleInstrumentedTest.kt
================================================
package com.vishalgaur.shoppingapp

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

/**
 * Instrumented test, which will execute on an Android device.
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
	@Test
	fun useAppContext() {
		// Context of the app under test.
		val appContext = InstrumentationRegistry.getInstrumentation().targetContext
		assertEquals("com.vishalgaur.shoppingapp", appContext.packageName)
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/LiveDataTestUtil.kt
================================================
package com.vishalgaur.shoppingapp

import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException

/* Copyright 2019 Google LLC.
   SPDX-License-Identifier: Apache-2.0 */
fun <T> LiveData<T>.getOrAwaitValue(
	time: Long = 2,
	timeUnit: TimeUnit = TimeUnit.SECONDS
): T {
	var data: T? = null
	val latch = CountDownLatch(1)
	val observer = object : Observer<T> {
		override fun onChanged(o: T?) {
			data = o
			latch.countDown()
			this@getOrAwaitValue.removeObserver(this)
		}
	}

	this.observeForever(observer)

	// Don't wait indefinitely if the LiveData is not set.
	if (!latch.await(time, timeUnit)) {
		throw TimeoutException("LiveData value was never set.")
	}

	@Suppress("UNCHECKED_CAST")
	return data as T
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/MainCoroutineRule.kt
================================================
package com.vishalgaur.shoppingapp

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import kotlin.coroutines.ContinuationInterceptor

@ExperimentalCoroutinesApi
class MainCoroutineRule : TestWatcher(), TestCoroutineScope by TestCoroutineScope() {

	override fun starting(description: Description?) {
		super.starting(description)
		Dispatchers.setMain(this.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher)
	}

	override fun finished(description: Description?) {
		super.finished(description)
		Dispatchers.resetMain()
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/RecyclerViewMatcherUtils.kt
================================================
package com.vishalgaur.shoppingapp

import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewAssertion
import androidx.test.espresso.matcher.BoundedMatcher
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers.`is`


fun atPosition(position: Int, itemMatcher: Matcher<View?>): Matcher<View?> {
	return object : BoundedMatcher<View?, RecyclerView>(RecyclerView::class.java) {
		override fun describeTo(description: Description) {
			description.appendText("has item at position $position: ")
			itemMatcher.describeTo(description)
		}

		override fun matchesSafely(view: RecyclerView): Boolean {
			val viewHolder = view.findViewHolderForAdapterPosition(position)
				?: // has no item on such position
				return false
			return itemMatcher.matches(viewHolder.itemView)
		}
	}
}

abstract class RecyclerViewItemAction: ViewAction {
	override fun getConstraints(): Matcher<View>? {
		return null
	}

	override fun getDescription(): String {
		return "Action on a specific Button"
	}
}

class RecyclerViewItemCountAssertion(private val expectedCount: Int) : ViewAssertion {
	override fun check(view: View, noViewFoundException: NoMatchingViewException?) {
		if (noViewFoundException != null) {
			throw noViewFoundException
		}
		val recyclerView = view as RecyclerView
		val adapter = recyclerView.adapter
		assertThat(adapter!!.itemCount, `is`(expectedCount))
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/FakeAuthRepository.kt
================================================
package com.vishalgaur.shoppingapp.data.source

import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.PhoneAuthCredential
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepoInterface
import com.vishalgaur.shoppingapp.data.utils.EmailMobileData
import com.vishalgaur.shoppingapp.data.utils.SignUpErrors
import com.vishalgaur.shoppingapp.data.utils.UserType

class FakeAuthRepository(private val sessionManager: ShoppingAppSessionManager) :
	AuthRepoInterface {

	private var emailMobileData = EmailMobileData()
	private var uData: UserData? = null

	override suspend fun refreshData() {
		// no implementation
	}

	override suspend fun signUp(userData: UserData) {
		uData = userData
		sessionManager.createLoginSession(
			userData.userId,
			userData.name,
			userData.mobile,
			false,
			userData.userType == UserType.SELLER.name
		)
	}

	override fun login(userData: UserData, rememberMe: Boolean) {
		uData = userData
		sessionManager.createLoginSession(
			userData.userId,
			userData.name,
			userData.mobile,
			rememberMe,
			userData.userType == UserType.SELLER.name
		)
	}

	override suspend fun checkEmailAndMobile(
		email: String,
		mobile: String,
		context: Context
	): SignUpErrors {
		// no implementation
		return SignUpErrors.NONE
	}

	override suspend fun checkLogin(mobile: String, password: String): UserData? {
		uData?.let {
			if (it.mobile == mobile && it.password == password) {
				return it
			}
		}
		return null
	}

	override suspend fun signOut() {
		uData = null
		sessionManager.logoutFromSession()
	}

	override suspend fun hardRefreshUserData() {
		// no implementation
	}

	override suspend fun insertProductToLikes(productId: String, userId: String): Result<Boolean> {
		uData?.let {
			if (it.userId == userId) {
				val likes = it.likes.toMutableList()
				likes.add(productId)
				it.likes = likes
				return Result.Success(true)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun removeProductFromLikes(
		productId: String,
		userId: String
	): Result<Boolean> {
		uData?.let {
			if (it.userId == userId) {
				val likes = it.likes.toMutableList()
				likes.remove(productId)
				it.likes = likes
				return Result.Success(true)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun insertAddress(
		newAddress: UserData.Address,
		userId: String
	): Result<Boolean> {
		uData?.let {
			if (it.userId == userId) {
				val addresses = it.addresses.toMutableList()
				addresses.add(newAddress)
				it.addresses = addresses
				return Result.Success(true)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun updateAddress(
		newAddress: UserData.Address,
		userId: String
	): Result<Boolean> {
		uData?.let {
			if (it.userId == userId) {
				val addresses = it.addresses.toMutableList()
				addresses.add(newAddress)
				val pos =
					it.addresses.indexOfFirst { address -> address.addressId == newAddress.addressId }
				if (pos >= 0) {
					addresses[pos] = newAddress
				}
				it.addresses = addresses
				return Result.Success(true)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun deleteAddressById(addressId: String, userId: String): Result<Boolean> {
		uData?.let {
			if (it.userId == userId) {
				val addresses = it.addresses.toMutableList()
				val pos = it.addresses.indexOfFirst { address -> address.addressId == addressId }
				if (pos >= 0) {
					addresses.removeAt(pos)
				}
				it.addresses = addresses
				return Result.Success(true)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun insertCartItemByUserId(
		cartItem: UserData.CartItem,
		userId: String
	): Result<Boolean> {
		uData?.let {
			if (it.userId == userId) {
				val cart = it.cart.toMutableList()
				cart.add(cartItem)
				it.cart = cart
				return Result.Success(true)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun updateCartItemByUserId(
		cartItem: UserData.CartItem,
		userId: String
	): Result<Boolean> {
		uData?.let {
			if (it.userId == userId) {
				val cart = it.cart.toMutableList()
				val pos = it.cart.indexOfFirst { item -> item.itemId == cartItem.itemId }
				if (pos >= 0) {
					cart[pos] = cartItem
				}
				it.cart = cart
				return Result.Success(true)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun deleteCartItemByUserId(itemId: String, userId: String): Result<Boolean> {
		uData?.let {
			if (it.userId == userId) {
				val cart = it.cart.toMutableList()
				val pos = it.cart.indexOfFirst { item -> item.itemId == itemId }
				if (pos >= 0) {
					cart.removeAt(pos)
				}
				it.cart = cart
				return Result.Success(true)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun getAddressesByUserId(userId: String): Result<List<UserData.Address>?> {
		uData?.let {
			if (it.userId == userId) {
				return Result.Success(it.addresses)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun getLikesByUserId(userId: String): Result<List<String>?> {
		uData?.let {
			if (it.userId == userId) {
				return Result.Success(it.likes)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override suspend fun getUserData(userId: String): Result<UserData?> {
		uData?.let {
			if (it.userId == userId) {
				return Result.Success(it)
			}
		}
		return Result.Error(Exception("User Not Found"))
	}

	override fun getFirebaseAuth(): FirebaseAuth {
		return Firebase.auth
	}

	override fun signInWithPhoneAuthCredential(
		credential: PhoneAuthCredential,
		isUserLoggedIn: MutableLiveData<Boolean>,
		context: Context
	) {
		// no implementation
	}

	override fun isRememberMeOn(): Boolean {
		// no implementation
		return true
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/FakeProductsDataSource.kt
================================================
package com.vishalgaur.shoppingapp.data.source

import android.net.Uri
import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.Result.Error
import com.vishalgaur.shoppingapp.data.Result.Success

class FakeProductsDataSource(private var products: MutableList<Product>? = mutableListOf()) :
	ProductDataSource {
	private val imagesStorage = mutableListOf<String>()
	override fun observeProducts(): LiveData<Result<List<Product>>?> {
		products?.let { pros ->
			val res = MutableLiveData(pros)
			return Transformations.map(res) {
				Success(it.toList())
			}
		}
		return MutableLiveData(Error(Exception()))
	}

	override fun observeProductsByOwner(ownerId: String): LiveData<Result<List<Product>>?> {
		products?.let { allPros ->
			val pros = allPros.filter { pr -> pr.owner == ownerId }
			val res = MutableLiveData(pros)
			return Transformations.map(res) {
				Success(it.toList())
			}
		}
		return MutableLiveData(Error(Exception()))
	}

	override suspend fun getAllProducts(): Result<List<Product>> {
		products?.let {
			return Success(it)
		}
		return Error(Exception("Products Not Found"))
	}

	override suspend fun refreshProducts() {
		// No implementation
	}

	override suspend fun getProductById(productId: String): Result<Product> {
		products?.let {
			val res = it.filter { product -> product.productId == productId }
			return if (res.isNotEmpty()) {
				Success(res[0])
			} else {
				Error(Exception("Product Not Found"))
			}
		}
		return Error(Exception("Product Not Found"))
	}

	override suspend fun insertProduct(newProduct: Product) {
		products?.add(newProduct)
	}

	override suspend fun updateProduct(proData: Product) {
		products?.let {
			val pos = it.indexOfFirst { product -> proData.productId == product.productId }
			it[pos] = proData
		}
	}

	override suspend fun deleteProduct(productId: String) {
		products?.let {
			val pos = it.indexOfFirst { product -> productId == product.productId }
			if (pos >= 0)
				it.removeAt(pos)
			else throw Exception("Product Not Found")
		}
	}

	override suspend fun getAllProductsByOwner(ownerId: String): Result<List<Product>> {
		val res = products?.filter { product ->
			product.owner == ownerId
		}
		return if (res != null) {
			Success(res)
		} else {
			Success(emptyList())
		}
	}

	override suspend fun deleteAllProducts() {
		products = mutableListOf()
	}

	override suspend fun insertMultipleProducts(data: List<Product>) {
		products?.addAll(data)
	}

	override suspend fun uploadImage(uri: Uri, fileName: String): Uri {
		val res = uri.toString() + fileName
		if (res.contains("invalidinvalidinvalid")) {
			throw Exception("Error uploading Images")
		}
		imagesStorage.add(res)
		return res.toUri()
	}

	override fun revertUpload(fileName: String) {
		val pos = imagesStorage.indexOfFirst { imageRef ->
			imageRef.contains(fileName)
		}
		if (pos >= 0) {
			imagesStorage.removeAt(pos)
		}
	}

	override fun deleteImage(imgUrl: String) {
		imagesStorage.remove(imgUrl)
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/FakeProductsRepository.kt
================================================
package com.vishalgaur.shoppingapp.data.source

import android.net.Uri
import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.Result.Error
import com.vishalgaur.shoppingapp.data.Result.Success
import com.vishalgaur.shoppingapp.data.source.repository.ProductsRepoInterface
import com.vishalgaur.shoppingapp.data.utils.StoreDataStatus
import kotlinx.coroutines.runBlocking
import java.util.*
import kotlin.collections.LinkedHashMap

class FakeProductsRepository : ProductsRepoInterface {

	var productsServiceData: LinkedHashMap<String, Product> = LinkedHashMap()
	private val imagesStorage = mutableListOf<String>()
	private val observableProducts = MutableLiveData<Result<List<Product>>>()

	override suspend fun refreshProducts(): StoreDataStatus {
		observableProducts.value = Success(productsServiceData.values.toList())
		return StoreDataStatus.DONE
	}

	override fun observeProducts(): LiveData<Result<List<Product>>?> {
		runBlocking { refreshProducts() }
		return observableProducts
	}

	override fun observeProductsByOwner(ownerId: String): LiveData<Result<List<Product>>?> {
		runBlocking { refreshProducts() }
		return Transformations.map(observableProducts) { products ->
			when (products) {
				is Result.Loading -> Result.Loading
				is Error -> Error(products.exception)
				is Success -> {
					val pros = products.data.filter { it.owner == ownerId }
					Success(pros)
				}
			}
		}
	}

	override suspend fun getAllProductsByOwner(ownerId: String): Result<List<Product>> {
		productsServiceData.values.let { pros ->
			val res = pros.filter { it.owner == ownerId }
			return Success(res)
		}
	}

	override suspend fun getProductById(productId: String, forceUpdate: Boolean): Result<Product> {
		productsServiceData[productId]?.let {
			return Success(it)
		}
		return Error(Exception("Product Not Found!"))
	}

	override suspend fun insertProduct(newProduct: Product): Result<Boolean> {
		productsServiceData[newProduct.productId] = newProduct
		return Success(true)
	}

	override suspend fun insertImages(imgList: List<Uri>): List<String> {
		val result = mutableListOf<String>()
		imgList.forEach { uri ->
			val uniId = UUID.randomUUID().toString()
			val fileName = uniId + uri.lastPathSegment?.split("/")?.last()
			val res = uri.toString() + fileName
			imagesStorage.add(res)
			result.add(res)
		}
		return result
	}

	override suspend fun updateProduct(product: Product): Result<Boolean> {
		productsServiceData[product.productId] = product
		return Success(true)
	}

	override suspend fun updateImages(newList: List<Uri>, oldList: List<String>): List<String> {
		val urlList = mutableListOf<String>()
		newList.forEach { uri ->
			if (!oldList.contains(uri.toString())) {
				val uniId = UUID.randomUUID().toString()
				val fileName = uniId + uri.lastPathSegment?.split("/")?.last()
				val res = uri.toString() + fileName
				imagesStorage.add(res)
				urlList.add(res)
			} else {
				urlList.add(uri.toString())
			}
		}
		oldList.forEach { imgUrl ->
			if (!newList.contains(imgUrl.toUri())) {
				imagesStorage.remove(imgUrl)
			}
		}
		return urlList
	}

	override suspend fun deleteProductById(productId: String): Result<Boolean> {
		productsServiceData.remove(productId)
		refreshProducts()
		return Success(true)
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/FakeUserDataSource.kt
================================================
package com.vishalgaur.shoppingapp.data.source

import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.Result.Error
import com.vishalgaur.shoppingapp.data.Result.Success
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.utils.EmailMobileData

class FakeUserDataSource(private var uData: UserData?) : UserDataSource {

	private var emailMobileData = EmailMobileData()

	override suspend fun addUser(userData: UserData) {
		uData = userData
	}

	override suspend fun getUserById(userId: String): Result<UserData?> {
		uData?.let {
			if (it.userId == userId) {
				return Success(it)
			}
		}
		return Error(Exception("User Not Found"))
	}

	override suspend fun getEmailsAndMobiles(): EmailMobileData {
		return emailMobileData
	}

	override suspend fun getUserByMobileAndPassword(
		mobile: String,
		password: String
	): MutableList<UserData> {
		val res = mutableListOf<UserData>()
		uData?.let {
			if (it.mobile == mobile && it.password == password) {
				res.add(it)
			}
		}
		return res
	}

	override suspend fun clearUser() {
		uData = null
	}

	override suspend fun getUserByMobile(phoneNumber: String): UserData? {
		return super.getUserByMobile(phoneNumber)
	}

	override fun updateEmailsAndMobiles(email: String, mobile: String) {
		emailMobileData.emails.add(email)
		emailMobileData.mobiles.add(mobile)
	}

	override suspend fun likeProduct(productId: String, userId: String) {
		uData?.let {
			if (it.userId == userId) {
				val likes = it.likes.toMutableList()
				likes.add(productId)
				it.likes = likes
			}
		}
	}

	override suspend fun dislikeProduct(productId: String, userId: String) {
		uData?.let {
			if (it.userId == userId) {
				val likes = it.likes.toMutableList()
				likes.remove(productId)
				it.likes = likes
			}
		}
	}

	override suspend fun insertAddress(newAddress: UserData.Address, userId: String) {
		uData?.let {
			if (it.userId == userId) {
				val addresses = it.addresses.toMutableList()
				addresses.add(newAddress)
				it.addresses = addresses
			}
		}
	}

	override suspend fun updateAddress(newAddress: UserData.Address, userId: String) {
		uData?.let { data ->
			if (data.userId == userId) {
				val addresses = data.addresses.toMutableList()
				val pos = data.addresses.indexOfFirst { it.addressId == newAddress.addressId }
				if (pos >= 0) {
					addresses[pos] = newAddress
				}
				data.addresses = addresses
			}
		}
	}

	override suspend fun deleteAddress(addressId: String, userId: String) {
		uData?.let { data ->
			if (data.userId == userId) {
				val addresses = data.addresses.toMutableList()
				val pos = data.addresses.indexOfFirst { it.addressId == addressId }
				if (pos >= 0) {
					addresses.removeAt(pos)
				}
				data.addresses = addresses
			}
		}
	}

	override suspend fun insertCartItem(newItem: UserData.CartItem, userId: String) {
		uData?.let {
			if (it.userId == userId) {
				val cart = it.cart.toMutableList()
				cart.add(newItem)
				it.cart = cart
			}
		}
	}

	override suspend fun updateCartItem(item: UserData.CartItem, userId: String) {
		uData?.let { data ->
			if (data.userId == userId) {
				val cart = data.cart.toMutableList()
				val pos = data.cart.indexOfFirst { it.itemId == item.itemId }
				if (pos >= 0) {
					cart[pos] = item
				}
				data.cart = cart
			}
		}
	}

	override suspend fun deleteCartItem(itemId: String, userId: String) {
		uData?.let { data ->
			if (data.userId == userId) {
				val cart = data.cart.toMutableList()
				val pos = data.cart.indexOfFirst { it.itemId == itemId }
				if (pos >= 0) {
					cart.removeAt(pos)
				}
				data.cart = cart
			}
		}
	}

	override suspend fun getAddressesByUserId(userId: String): Result<List<UserData.Address>?> {
		uData?.let {
			if (it.userId == userId) {
				return Success(it.addresses)
			}
		}
		return Error(Exception("User Not Found"))
	}

	override suspend fun getLikesByUserId(userId: String): Result<List<String>?> {
		uData?.let {
			if (it.userId == userId) {
				return Success(it.likes)
			}
		}
		return Error(Exception("User Not Found"))
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/repository/AuthRepositoryTest.kt
================================================
package com.vishalgaur.shoppingapp.data.source.repository

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.google.firebase.FirebaseApp
import com.vishalgaur.shoppingapp.data.Result.Error
import com.vishalgaur.shoppingapp.data.Result.Success
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.FakeUserDataSource
import com.vishalgaur.shoppingapp.data.utils.SignUpErrors
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.nullValue
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

@ExperimentalCoroutinesApi
class AuthRepositoryTest {
	private val userSeller = UserData(
		"weoifhwenf29385",
		"Seller Name",
		"+919999990000",
		"somemail@mail.com",
		"12345",
		emptyList(),
		emptyList(),
		emptyList(),
		"SELLER",
	)
	private val userCustomer = UserData(
		"dwoeihwjklvn48329752",
		"Customer Name",
		"+919090909090",
		"somemail1232@mail.com",
		"12345",
		emptyList(),
		emptyList(),
		emptyList(),
		"CUSTOMER",
	)

	private lateinit var context: Context

	private lateinit var userLocalDataSource: FakeUserDataSource
	private lateinit var authRemoteDataSource: FakeUserDataSource
	private lateinit var sessionManager: ShoppingAppSessionManager

	// class under test
	private lateinit var authRepository: AuthRepository

	@Before
	fun createRepository() {
		context = ApplicationProvider.getApplicationContext()
		FirebaseApp.initializeApp(ApplicationProvider.getApplicationContext())
		userLocalDataSource = FakeUserDataSource(userSeller)
		authRemoteDataSource = FakeUserDataSource(userCustomer)
		sessionManager = ShoppingAppSessionManager(ApplicationProvider.getApplicationContext())

		authRepository = AuthRepository(
			userLocalDataSource,
			authRemoteDataSource,
			sessionManager
		)
	}

	@Test
	fun login_getUserDetailFromSession() = runBlockingTest {
		authRepository.login(userSeller, true)
		val result = sessionManager.getUserDataFromSession()

		assertThat(result["userName"], `is`(userSeller.name))
		assertThat(result["userId"], `is`(userSeller.userId))
		assertThat(result["userMobile"], `is`(userSeller.mobile))
	}

	@Test
	fun singUp_addsUserToSources() = runBlockingTest {
		authRepository.signUp(userCustomer)

		val resultSession = sessionManager.getUserDataFromSession()
		assertThat(resultSession["userName"], `is`(userCustomer.name))
		assertThat(resultSession["userId"], `is`(userCustomer.userId))
		assertThat(resultSession["userMobile"], `is`(userCustomer.mobile))

		val localRes = userLocalDataSource.getUserById(userCustomer.userId)
		assertThat(localRes, `is`(Success(userCustomer)))

		val remoteRes = authRemoteDataSource.getUserById(userCustomer.userId)
		assertThat(remoteRes, `is`(Success(userCustomer)))
	}

	@Test
	fun checkEmailAndMobile_existingEmail_returnsError() {
		authRemoteDataSource.updateEmailsAndMobiles("mail123@mail.com", "+919999988888")
		runOnUiThread {
			runBlockingTest {
				val result =
					authRepository.checkEmailAndMobile("mail123@mail.com", "+919685", context)
				assertThat(result, `is`(SignUpErrors.SERR))
			}
		}
	}

	@Test
	fun checkEmailAndMobile_existingMobile_returnsError() {
		authRemoteDataSource.updateEmailsAndMobiles("mail123@mail.com", "+919999988888")
		runOnUiThread {
			runBlockingTest {
				val result =
					authRepository.checkEmailAndMobile("mail999@mail.com", "+919999988888", context)
				assertThat(result, `is`(SignUpErrors.SERR))
			}
		}
	}

	@Test
	fun checkEmailAndMobile_existingMobileAndEmail_returnsError() {
		authRemoteDataSource.updateEmailsAndMobiles("mail123@mail.com", "+919999988888")
		runOnUiThread {
			runBlockingTest {
				val result =
					authRepository.checkEmailAndMobile("mail123@mail.com", "+919999988888", context)
				assertThat(result, `is`(SignUpErrors.SERR))
			}
		}
	}

	@Test
	fun checkEmailAndMobile_newData_returnsError() {
		authRemoteDataSource.updateEmailsAndMobiles("mail123@mail.com", "+919999988888")
		runOnUiThread {
			runBlockingTest {
				val result =
					authRepository.checkEmailAndMobile(
						"somemail123@mail.com",
						"+919999977777",
						context
					)
				assertThat(result, `is`(SignUpErrors.NONE))
			}
		}
	}

	@Test
	fun checkLogin_existingUser_returnsData() = runBlockingTest {
		val result = authRepository.checkLogin(userCustomer.mobile, userCustomer.password)
		assertThat(result, `is`(userCustomer))
	}

	@Test
	fun checkLogin_newCredentials_returnsNull() = runBlockingTest {
		val result = authRepository.checkLogin("+919879879879", "sdygt4")
		assertThat(result, `is`(nullValue()))
	}

	@Test
	fun signOut_clearsSessionAndData() = runBlockingTest {
		authRepository.signOut()

		val sessionRes = sessionManager.isLoggedIn()
		val localRes = userLocalDataSource.getUserById(userSeller.userId)

		assertThat(sessionRes, `is`(false))
		if (localRes is Success)
			assert(false)
		else if (localRes is Error) {
			assertEquals(localRes.exception.message, "User Not Found")
		}
	}

	@Test
	fun getLikes_returnsLikes() = runBlockingTest {
		authRepository.signUp(userCustomer)
		val res = authRepository.getLikesByUserId(userCustomer.userId)
		if (res is Success) {
			assertThat(res.data, `is`(userCustomer.likes))
		} else {
			assert(false)
		}
	}

	@Test
	fun likeProduct() = runBlockingTest {
		authRepository.signUp(userCustomer)
		authRepository.insertProductToLikes("some-id", userCustomer.userId)
		val res = authRepository.getLikesByUserId(userCustomer.userId)
		if (res is Success) {
			assertThat(res.data?.size, `is`(1))
		} else {
			assert(false)
		}
	}

	@Test
	fun dislikeProduct() = runBlockingTest {
		authRepository.signUp(userCustomer)
		authRepository.insertProductToLikes("some-id", userCustomer.userId)
		authRepository.removeProductFromLikes("some-id", userCustomer.userId)
		val res = authRepository.getLikesByUserId(userCustomer.userId)
		if (res is Success) {
			assertThat(res.data?.contains("some-id"), `is`(false))
		} else {
			assert(false)
		}
	}

	@Test
	fun getAddresses() = runBlockingTest {
		authRepository.signUp(userCustomer)
		val res = authRepository.getAddressesByUserId(userCustomer.userId)
		if (res is Success) {
			assertThat(res.data, `is`(userCustomer.addresses))
		} else {
			assert(false)
		}
	}

	@Test
	fun addAddress() = runBlockingTest {
		authRepository.signUp(userCustomer)
		val address = UserData.Address(
			"id123-add",
			"namefirst",
			"lname",
			"IN",
			"2341 weg",
			"",
			"kanopwe",
			"up",
			"209876",
			"+919999988888"
		)
		authRepository.insertAddress(address, userCustomer.userId)
		val res = authRepository.getAddressesByUserId(userCustomer.userId)
		if (res is Success) {
			assertThat(res.data?.size, `is`(1))
		} else {
			assert(false)
		}
	}

	@Test
	fun removeAddress() = runBlockingTest {
		authRepository.signUp(userCustomer)
		val address = UserData.Address(
			"id123-add",
			"namefirst",
			"lname",
			"IN",
			"2341 weg",
			"",
			"kanopwe",
			"up",
			"209876",
			"+919999988888"
		)
		authRepository.insertAddress(address, userCustomer.userId)
		authRepository.deleteAddressById(address.addressId, userCustomer.userId)
		val res = authRepository.getAddressesByUserId(userCustomer.userId)
		if (res is Success) {
			assertThat(res.data?.contains(address), `is`(false))
		} else {
			assert(false)
		}
	}

	@Test
	fun updateAddress() = runBlockingTest {
		authRepository.signUp(userCustomer)
		val address = UserData.Address(
			"id123-add",
			"namefirst",
			"lname",
			"IN",
			"2341 weg",
			"",
			"kanopwe",
			"up",
			"209876",
			"+919999988888"
		)
		val newAddress = UserData.Address(
			"id123-add",
			"namefirst",
			"lname",
			"IN",
			"2341 wesfgeg",
			"vdfg, heth",
			"kanopwe",
			"up",
			"209876",
			"+919999988888"
		)
		authRepository.insertAddress(address, userCustomer.userId)
		authRepository.updateAddress(newAddress, userCustomer.userId)
		val res = authRepository.getAddressesByUserId(userCustomer.userId)
		if (res is Success) {
			assertThat(res.data?.contains(newAddress), `is`(true))
		} else {
			assert(false)
		}
	}

	@Test
	fun addItemToCart() = runBlockingTest {
		authRepository.signUp(userCustomer)
		val item = UserData.CartItem(
			"item-id-123",
			"pro-122",
			"owner-1213",
			1,
			"black",
			7
		)
		authRepository.insertCartItemByUserId(item, userCustomer.userId)
		val userRes = userLocalDataSource.getUserById(userCustomer.userId)
		if (userRes is Success) {
			val data = userRes.data
			if (data != null) {
				val cart = data.cart
				assertThat(cart.contains(item), `is`(true))
			} else {
				assert(false)
			}
		} else {
			assert(false)
		}
	}

	@Test
	fun removeItemFromCart() = runBlockingTest {
		authRepository.signUp(userCustomer)
		val item = UserData.CartItem(
			"item-id-123",
			"pro-122",
			"owner-1213",
			1,
			"black",
			7
		)
		authRepository.insertCartItemByUserId(item, userCustomer.userId)
		authRepository.deleteCartItemByUserId(item.itemId, userCustomer.userId)
		val userRes = userLocalDataSource.getUserById(userCustomer.userId)
		if (userRes is Success) {
			val data = userRes.data
			if (data != null) {
				val cart = data.cart
				assertThat(cart.contains(item), `is`(false))
			} else {
				assert(false)
			}
		} else {
			assert(false)
		}
	}

	@Test
	fun updateItemInCart() = runBlockingTest {
		authRepository.signUp(userCustomer)
		val item = UserData.CartItem(
			"item-id-123",
			"pro-122",
			"owner-1213",
			1,
			"black",
			7
		)
		val newItem = UserData.CartItem(
			"item-id-123",
			"pro-122",
			"owner-1213",
			5,
			"black",
			7
		)
		authRepository.insertCartItemByUserId(item, userCustomer.userId)
		authRepository.updateCartItemByUserId(newItem, userCustomer.userId)
		val userRes = userLocalDataSource.getUserById(userCustomer.userId)
		if (userRes is Success) {
			val data = userRes.data
			if (data != null) {
				val cart = data.cart
				assertThat(cart.contains(newItem), `is`(true))
			} else {
				assert(false)
			}
		} else {
			assert(false)
		}
	}

}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/repository/ProductsRepositoryTest.kt
================================================
package com.vishalgaur.shoppingapp.data.source.repository

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.core.net.toUri
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import com.vishalgaur.shoppingapp.ERR_UPLOAD
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.source.FakeProductsDataSource
import com.vishalgaur.shoppingapp.data.utils.StoreDataStatus
import com.vishalgaur.shoppingapp.getOrAwaitValue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.greaterThan
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test

@ExperimentalCoroutinesApi
class ProductsRepositoryTest {
	private val pro1 = Product(
		"pro-owner1-shoe-101",
		"Shoe Name 101",
		"owner1",
		"some description",
		"Shoes",
		250.0,
		300.0,
		listOf(5, 6, 7, 8),
		listOf("Red", "Blue"),
		listOf("http://image-ref-uri/shoe-101-01.jpg", "http://image-ref-uri/-shoe-101-02.jpg"),
		2.5
	)
	private val pro2 = Product(
		"pro-owner1-slipper-101",
		"Slipper Name 101",
		"owner1",
		"some description",
		"Slippers",
		50.0,
		80.0,
		listOf(6, 7, 8),
		listOf("Black", "Blue"),
		listOf(
			"http://image-ref-uri/-slipper-101-01.jpg",
			"http://image-ref-uri/-slipper-101-02.jpg"
		),
		4.0
	)
	private val pro3 = Product(
		"pro-owner1-shoe-102",
		"Shoe Name 102",
		"owner2",
		"some description",
		"Shoes",
		450.0,
		600.0,
		listOf(4, 5, 7, 8, 10),
		listOf("Red", "Blue", "White"),
		listOf("http://image-ref-uri/-shoe-102-01.jpg", "http://image-ref-uri/-shoe-102-02.jpg"),
		3.5
	)

	private lateinit var productsLocalDataSource: FakeProductsDataSource
	private lateinit var productsRemoteDataSource: FakeProductsDataSource

	// class under test
	private lateinit var productsRepository: ProductsRepository

	@get:Rule
	val instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun createRepository() {
		productsLocalDataSource = FakeProductsDataSource(mutableListOf())
		productsRemoteDataSource = FakeProductsDataSource(mutableListOf(pro1, pro3))

		productsRepository = ProductsRepository(productsRemoteDataSource, productsLocalDataSource)
	}

	@Test
	fun getProductsById_invalidId_returnsError() = runBlockingTest {
		val resultRes = productsRepository.getProductById("invalidId", false)
		if (resultRes is Result.Success)
			assert(false)
		else if (resultRes is Result.Error) {
			assertEquals(resultRes.exception.message, "Product Not Found")
		}
	}

	@Test
	fun getProductsById_validId_returnsProduct() = runBlockingTest {
		productsRepository.insertProduct(pro1)
		val resultRes = productsRepository.getProductById(pro1.productId, false)
		if (resultRes is Result.Success) {
			assertThat(resultRes.data, `is`(pro1))
		} else if (resultRes is Result.Error) {
			assert(false)
		}
	}

	@Test
	fun insertProduct_returnsSuccess() = runBlockingTest {
		val insertRes = productsRepository.insertProduct(pro1)
		if (insertRes is Result.Success) {
			assertThat(insertRes.data, `is`(true))
		} else {
			assert(false)
		}
	}

	@Test
	fun insertImages_returnsSuccess() = runBlockingTest {
		val result = productsRepository.insertImages(pro1.images.map { it.toUri() })
		assertThat(result.size, `is`(pro1.images.size))
	}

	@Test
	fun insertImages_invalidImages_returnsError() = runBlockingTest {
		val result =
			productsRepository.insertImages(listOf("http://image-ref-uri/dwoeiovnwi-invalidinvalidinvalid/weoifhowf".toUri()))
		assertThat(result[0], `is`(ERR_UPLOAD))
	}

	@Test
	fun updateProduct_returnsSuccess() = runBlockingTest {
		productsRepository.insertProduct(pro2)
		val updatedPro = pro2
		updatedPro.availableSizes = listOf(5, 6, 10, 12)
		val insertRes = productsRepository.updateProduct(updatedPro)
		if (insertRes is Result.Success) {
			assertThat(insertRes.data, `is`(true))
		} else {
			assert(false)
		}
	}

	@Test
	fun updateImages_returnsList() = runBlockingTest {
		val oldList = productsRepository.insertImages(pro1.images.map { it.toUri() })
		val result = productsRepository.updateImages(pro3.images.map { it.toUri() }, oldList)
		assertThat(result.size, `is`(pro3.images.size))
	}

	@Test
	fun updateImages_invalidImage_returnsError() = runBlockingTest {
		val oldList = productsRepository.insertImages(pro1.images.map { it.toUri() })
		val newList = oldList.toMutableList()
		newList[0] = "http://csifduoskjgn/invalidinvalidinvalid/wehoiw"
		val result = productsRepository.updateImages(newList.map { it.toUri() }, oldList)
		assertThat(result[0], `is`(ERR_UPLOAD))
	}

	@Test
	fun deleteProductById_returnsSuccess() = runBlockingTest {
		productsRepository.insertProduct(pro1)
		productsRepository.insertProduct(pro2)
		val result = productsRepository.deleteProductById(pro1.productId)
		assert(result is Result.Success)
	}

	@Test
	fun deleteProductById_invalidId_returnsError() = runBlockingTest {
		productsRepository.insertProduct(pro1)
		productsRepository.insertProduct(pro2)
		val result = productsRepository.deleteProductById(pro3.productId)
		assert(result is Result.Error)
	}

	@Test
	fun refreshProducts_returnsSuccess() = runBlockingTest {
		val result = productsRepository.refreshProducts()
		assertThat(result, `is`(StoreDataStatus.DONE))
	}

	@Test
	fun observeProducts_noData_returnsNoData() = runBlockingTest {
		productsLocalDataSource.deleteAllProducts()
		val result = productsRepository.observeProducts().getOrAwaitValue()
		if (result is Result.Success) {
			assertThat(result.data.size, `is`(0))
		} else {
			assert(false)
		}
	}

	@Test
	fun observeProducts_hasData_returnsSuccessWithData() = runBlockingTest {
		val initialValue = productsRepository.observeProducts().getOrAwaitValue()

		val insertRes = async { productsRepository.insertProduct(pro3) }
		insertRes.await()
		val refreshRes = async { productsRepository.refreshProducts() }
		assertThat(refreshRes.await(), `is`(StoreDataStatus.DONE))

		val newValue = productsRepository.observeProducts().getOrAwaitValue()

		assertNotEquals(initialValue.toString(), newValue.toString())
		if (initialValue is Result.Success) {
			assertThat(initialValue.data.size, `is`(0))
		} else {
			assert(false)
		}
		if (newValue is Result.Success) {
			assertThat(newValue.data.size, `is`(greaterThan(0)))
		} else {
			assert(false)
		}
	}

	@Test
	fun observeProductsByOwner_noData_returnsNoData() = runBlockingTest {
		productsLocalDataSource.deleteAllProducts()
		val result = productsRepository.observeProductsByOwner(pro1.owner).getOrAwaitValue()
		if (result is Result.Success) {
			assertThat(result.data.size, `is`(0))
		} else {
			assert(false)
		}
	}

	@Test
	fun observeProductsByOwner_hasData_returnsSuccessWithData() = runBlockingTest {
		val initialValue = productsRepository.observeProductsByOwner(pro3.owner).getOrAwaitValue()

		val insertRes = async { productsRepository.insertProduct(pro3) }
		insertRes.await()
		val refreshRes = async { productsRepository.refreshProducts() }
		assertThat(refreshRes.await(), `is`(StoreDataStatus.DONE))

		val newValue = productsRepository.observeProductsByOwner(pro3.owner).getOrAwaitValue()

		assertNotEquals(initialValue.toString(), newValue.toString())
		if (initialValue is Result.Success) {
			assertThat(initialValue.data.size, `is`(0))
		} else {
			assert(false)
		}
		if (newValue is Result.Success) {
			assertThat(newValue.data.size, `is`(greaterThan(0)))
		} else {
			assert(false)
		}
	}

	@Test
	fun getAllProductsByOwner_noData_returnsNoData() = runBlockingTest {
		productsLocalDataSource.deleteAllProducts()
		val result = productsRepository.getAllProductsByOwner(pro1.owner)
		if (result is Result.Success) {
			assertThat(result.data.size, `is`(0))
		} else {
			assert(false)
		}
	}

	@Test
	fun getAllProductsByOwner_hasData_returnsData() = runBlockingTest {
		productsRepository.refreshProducts()
		val result = productsRepository.getAllProductsByOwner(pro1.owner)
		if (result is Result.Success) {
			assertThat(result.data.size, `is`(greaterThan(0)))
		} else {
			assert(false)
		}
	}

}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/ui/home/HomeFragmentTest.kt
================================================
package com.vishalgaur.shoppingapp.ui.home

import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.core.view.isVisible
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.navigation.testing.TestNavHostController
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.UiController
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.vishalgaur.shoppingapp.*
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.FakeAuthRepository
import com.vishalgaur.shoppingapp.data.source.FakeProductsRepository
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepoInterface
import com.vishalgaur.shoppingapp.data.source.repository.ProductsRepoInterface
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.Matchers.`is`
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi
class HomeFragmentTest {
	private lateinit var homeScenario: FragmentScenario<HomeFragment>
	private lateinit var navController: NavController
	private lateinit var sessionManager: ShoppingAppSessionManager
	private lateinit var productsRepository: ProductsRepoInterface
	private lateinit var authRepository: AuthRepoInterface
	private val context = ApplicationProvider.getApplicationContext<ShoppingApplication>()

	private val userCustomer = UserData(
		"sdjm43892yfh948ehod",
		"Vishal",
		"+919999988888",
		"vishal@somemail.com",
		"dh94328hd",
		ArrayList(),
		ArrayList(),
		ArrayList(),
		"CUSTOMER"
	)
	private val userSeller = UserData(
		"user1234selller",
		"Some Name",
		"+919999988888",
		"somemail123seller@somemail.com",
		"1234",
		emptyList(),
		emptyList(),
		emptyList(),
		"SELLER"
	)

	private val pro1 = Product(
		"pro-owner1-shoe-101",
		"Shoe Name 101",
		"user1234selller",
		"some description",
		"Shoes",
		250.0,
		300.0,
		listOf(5, 6, 7, 8),
		listOf("Red", "Blue"),
		listOf("http://image-ref-uri/shoe-101-01.jpg", "http://image-ref-uri/-shoe-101-02.jpg"),
		2.5
	)
	private val pro2 = Product(
		"pro-owner1-slipper-101",
		"Slipper Name 101",
		"owner1",
		"some description",
		"Slippers",
		50.0,
		80.0,
		listOf(6, 7, 8),
		listOf("Black", "Blue"),
		listOf(
			"http://image-ref-uri/-slipper-101-01.jpg",
			"http://image-ref-uri/-slipper-101-02.jpg"
		),
		4.0
	)


	@get:Rule
	var instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun setUp() {
		sessionManager = ShoppingAppSessionManager(context)
		authRepository = FakeAuthRepository(sessionManager)
		productsRepository = FakeProductsRepository()
		ServiceLocator.productsRepository = productsRepository
	}

	@After
	fun cleanUp() = runBlockingTest {
		authRepository.signOut()
		ServiceLocator.resetRepository()
	}

	@Test
	fun userCustomer_hideFABandEditDeleteButtons() = runBlockingTest {
		insertProducts()
		loginCustomer()

		onView(withId(R.id.home_fab_add_product)).check(matches(withEffectiveVisibility(Visibility.GONE)))

		//testing recyclerview items
		onView(withId(R.id.products_recycler_view))
			.perform(
				RecyclerViewActions.actionOnItemAtPosition<ProductAdapter.ItemViewHolder>(
					0,
					object : RecyclerViewItemAction() {
						override fun perform(uiController: UiController?, view: View) {
							val editButton: ImageView = view.findViewById(R.id.product_edit_button)
							val deleteButton: ImageView =
								view.findViewById(R.id.product_delete_button)
							assertThat(editButton.isVisible, `is`(false))
							assertThat(deleteButton.isVisible, `is`(false))
						}
					})
			)
	}

	@Test
	fun userSeller_showFABandEditDeleteButtons() = runBlockingTest {
		insertProducts()
		loginSeller()

		onView(withId(R.id.home_fab_add_product)).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
		//testing recyclerview items
		onView(withId(R.id.products_recycler_view))
			.perform(
				RecyclerViewActions.actionOnItemAtPosition<ProductAdapter.ItemViewHolder>(
					0,
					object : RecyclerViewItemAction() {
						override fun perform(uiController: UiController?, view: View) {
							val editButton: ImageView = view.findViewById(R.id.product_edit_button)
							val deleteButton: ImageView =
								view.findViewById(R.id.product_delete_button)
							assertThat(editButton.isVisible, `is`(true))
							assertThat(deleteButton.isVisible, `is`(true))
						}
					})
			)
	}

	@Test
	fun onFABClick_openCategoryDialog() {
		loginSeller()

		onView(withId(R.id.home_fab_add_product)).perform(click())
		onView(withText(R.string.pro_cat_dialog_title)).inRoot(isDialog())
			.check(matches(isDisplayed()))
	}

	@Test
	fun onSelectCategory_openAddProductFragment() {
		loginSeller()

		onView(withId(R.id.home_fab_add_product)).perform(click())
		onView(withText(R.string.pro_cat_dialog_ok_btn)).inRoot(isDialog())
			.check(matches(isDisplayed())).perform(click())

		assertThat(navController.currentDestination?.id, `is`(R.id.addEditProductFragment))
	}

	@Test
	fun onFilterClick_openFilterDialog() = runBlockingTest{
		insertProducts()
		loginCustomer()
		onView(withId(R.id.home_filter)).perform(click())
		onView(withText("Shoes")).inRoot(isDialog())
			.perform(click())
		onView(withText(R.string.pro_cat_dialog_ok_btn)).inRoot(isDialog())
			.perform(click())
		onView(withId(R.id.products_recycler_view)).check(RecyclerViewItemCountAssertion(1))

	}

	@Test
	fun onFilter_filterResults() {
		loginCustomer()
		onView(withId(R.id.home_filter)).perform(click())
		onView(withText(R.string.pro_cat_dialog_title)).inRoot(isDialog())
			.check(matches(isDisplayed()))
	}

	@Test
	fun enterSearch_filtersResults() {
		runBlocking {
			insertProducts()
			loginCustomer()
			onView(withId(R.id.home_search_edit_text)).perform(typeText("slipper"))
			delay(500)
			onView(withId(R.id.products_recycler_view)).check(RecyclerViewItemCountAssertion(1))
		}
	}

	private fun loginSeller() {
		authRepository.login(userSeller, true)
		ServiceLocator.authRepository = authRepository
		setScenarioAndNav()
	}

	private fun loginCustomer() {
		authRepository.login(userCustomer, true)
		ServiceLocator.authRepository = authRepository
		setScenarioAndNav()
	}

	private fun setScenarioAndNav() {
		homeScenario = launchFragmentInContainer(Bundle(), R.style.Theme_ShoppingApp)
		navController = TestNavHostController(context)
		runOnUiThread {
			navController.setGraph(R.navigation.home_nav_graph)
			homeScenario.onFragment {
				Navigation.setViewNavController(it.requireView(), navController)
			}
		}
	}

	private suspend fun insertProducts() {
		productsRepository.insertProduct(pro1)
		productsRepository.insertProduct(pro2)
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/ui/home/ProductDetailsFragmentTest.kt
================================================
package com.vishalgaur.shoppingapp.ui.home

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.fragment.app.testing.FragmentScenario
import androidx.navigation.NavController
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vishalgaur.shoppingapp.ServiceLocator
import com.vishalgaur.shoppingapp.ShoppingApplication
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.FakeAuthRepository
import com.vishalgaur.shoppingapp.data.source.FakeProductsRepository
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepoInterface
import com.vishalgaur.shoppingapp.data.source.repository.ProductsRepoInterface
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi
class ProductDetailsFragmentTest {
	private lateinit var productDetailScenario: FragmentScenario<ProductDetailsFragment>
	private lateinit var navController: NavController
	private lateinit var sessionManager: ShoppingAppSessionManager
	private lateinit var productsRepository: ProductsRepoInterface
	private lateinit var authRepository: AuthRepoInterface
	private val context = ApplicationProvider.getApplicationContext<ShoppingApplication>()

	private val pro1 = Product(
		"pro-owner1-shoe-101",
		"Shoe Name 101",
		"user1234selller",
		"some description",
		"Shoes",
		250.0,
		300.0,
		listOf(5, 6, 7, 8),
		listOf("Red", "Blue"),
		listOf("http://image-ref-uri/shoe-101-01.jpg", "http://image-ref-uri/-shoe-101-02.jpg"),
		2.5
	)

	private val userCustomer = UserData(
		"sdjm43892yfh948ehod",
		"Vishal",
		"+919999988888",
		"vishal@somemail.com",
		"dh94328hd",
		ArrayList(),
		ArrayList(),
		ArrayList(),
		"CUSTOMER"
	)
	private val userSeller = UserData(
		"user1234selller",
		"Some Name",
		"+919999988888",
		"somemail123seller@somemail.com",
		"1234",
		emptyList(),
		emptyList(),
		emptyList(),
		"SELLER"
	)

	@get:Rule
	var instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun setUp() {
		sessionManager = ShoppingAppSessionManager(context)
		authRepository = FakeAuthRepository(sessionManager)
		productsRepository = FakeProductsRepository()
		ServiceLocator.productsRepository = productsRepository
	}

	@After
	fun cleanUp() = runBlockingTest {
		authRepository.signOut()
		ServiceLocator.resetRepository()
	}

	private suspend fun insertProducts() {
		productsRepository.insertProduct(pro1)
	}
}


================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/ui/loginSignup/LoginFragmentTest.kt
================================================
package com.vishalgaur.shoppingapp.ui.loginSignup

import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.navigation.testing.TestNavHostController
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import androidx.test.platform.app.InstrumentationRegistry
import com.vishalgaur.shoppingapp.MOB_ERROR_TEXT
import com.vishalgaur.shoppingapp.R
import com.vishalgaur.shoppingapp.clickClickableSpan
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import org.hamcrest.Matchers.`is`
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class LoginFragmentTest {
	private lateinit var loginScenario: FragmentScenario<LoginFragment>
	private lateinit var navController: NavController
	private lateinit var sessionManager: ShoppingAppSessionManager

	@Before
	fun setUp() {
		sessionManager = ShoppingAppSessionManager(ApplicationProvider.getApplicationContext())
		sessionManager.logoutFromSession()
		loginScenario = launchFragmentInContainer(themeResId = R.style.Theme_ShoppingApp)
		navController = TestNavHostController(ApplicationProvider.getApplicationContext())

		runOnUiThread {
			navController.setGraph(R.navigation.signup_nav_graph)
			loginScenario.onFragment {
				Navigation.setViewNavController(it.requireView(), navController)
			}
		}
	}

	@Test
	fun useAppContext() {
		val context = InstrumentationRegistry.getInstrumentation().targetContext
		Assert.assertEquals("com.vishalgaur.shoppingapp", context.packageName)
	}

	@Test
	fun userCanEnterMobile() {
		insertInMobileEditText("8976527465")
	}

	@Test
	fun userCanEnterPassword() {
		insertInPwdEditText("dh239048fy")
	}

	@Test
	fun userCanClickRemSwitch() {
		clickRememberSwitch()
	}

	@Test
	fun userCanClickSignUpText() {
		clickSignUpText()
	}

	@Test
	fun userCanClickForgotTextView() {
		clickForgotTextView()
	}

	@Test
	fun userCanClickLoginButton() {
		clickLoginButton()
	}

	@Test
	fun onSignUpClick_navigateToSignUpFragment() {
		clickSignUpText()
		assertThat(navController.currentDestination?.id, `is`(R.id.SignupFragment))
	}

	@Test
	fun onLogin_emptyForm_showsError() {
		clickLoginButton()

		onView(withId(R.id.login_error_text_view)).check(matches(isDisplayed()))
	}

	@Test
	fun onLogin_invalidMobile_showsError() {
		insertInMobileEditText("  467856 ")
		insertInPwdEditText("fd3g24")
		clickLoginButton()

		onView(withId(R.id.login_mobile_edit_text)).check(matches(hasErrorText(`is`(MOB_ERROR_TEXT))))
	}

	@Test
	fun onLogin_validData_showsNoError() {
		Intents.init()

		insertInMobileEditText("9966339966")
		insertInPwdEditText("1234")
		clickLoginButton()

		intended(hasComponent(OtpActivity::class.java.name))
	}

	private fun insertInMobileEditText(phone: String) =
		onView(withId(R.id.login_mobile_edit_text)).perform(
			scrollTo(),
			clearText(),
			typeText(phone)
		)

	private fun insertInPwdEditText(pwd: String) =
		onView(withId(R.id.login_password_edit_text)).perform(
			scrollTo(),
			clearText(),
			typeText(pwd)
		)

	private fun clickRememberSwitch() =
		onView(withId(R.id.login_rem_switch))
			.perform(scrollTo(), click())

	private fun clickForgotTextView() =
		onView(withId(R.id.login_forgot_tv))
			.perform(scrollTo(), click())

	private fun clickLoginButton() =
		onView(withId(R.id.login_login_btn))
			.perform(scrollTo(), click())

	private fun clickSignUpText() =
		onView(withId(R.id.login_signup_text_view)).perform(
			scrollTo(),
			clickClickableSpan("Sign Up")
		)
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/ui/loginSignup/SignupFragmentTest.kt
================================================
package com.vishalgaur.shoppingapp.ui.loginSignup

import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.navigation.testing.TestNavHostController
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import androidx.test.platform.app.InstrumentationRegistry
import com.vishalgaur.shoppingapp.EMAIL_ERROR_TEXT
import com.vishalgaur.shoppingapp.MOB_ERROR_TEXT
import com.vishalgaur.shoppingapp.R
import com.vishalgaur.shoppingapp.clickClickableSpan
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import org.hamcrest.Matchers.`is`
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class SignupFragmentTest {
	private lateinit var signUpScenario: FragmentScenario<SignupFragment>
	private lateinit var navController: NavController
	private lateinit var sessionManager: ShoppingAppSessionManager

	@Before
	fun setUp() {
		sessionManager = ShoppingAppSessionManager(ApplicationProvider.getApplicationContext())
		sessionManager.logoutFromSession()
		signUpScenario = launchFragmentInContainer(themeResId = R.style.Theme_ShoppingApp)
		navController = TestNavHostController(ApplicationProvider.getApplicationContext())

		runOnUiThread {
			navController.setGraph(R.navigation.signup_nav_graph)
			signUpScenario.onFragment {
				Navigation.setViewNavController(it.requireView(), navController)
			}
		}
	}

	@Test
	fun useAppContext() {
		val context = InstrumentationRegistry.getInstrumentation().targetContext
		assertEquals("com.vishalgaur.shoppingapp", context.packageName)
	}

	@Test
	fun userCanEnterName() {
		insertInNameEditText("Vishal Gaur  ")
	}

	@Test
	fun userCanEnterMobile() {
		insertInMobileEditText("8976527465")
	}

	@Test
	fun userCanEnterEmail() {
		insertInEmailEditText("  weuiyjkh@ujhyew.dciwe")
	}

	@Test
	fun userCanEnterPassword() {
		insertInPwdEditText("dh239048fy")
	}

	@Test
	fun userCanEnterInCnfPassword() {
		insertInCnfPwdEditText("con34uyf98")
	}

	@Test
	fun userCanClickTermsSwitch() {
		clickTermsSwitch()
	}

	@Test
	fun userCanClickSellerSwitch() {
		clickSellerSwitch()
	}

	@Test
	fun userCanClickSignUp() {
		clickSignUpButton()
	}

	@Test
	fun userCanClickLogInText() {
		clickLoginText()
	}

	@Test
	fun onLoginClick_navigateToLoginFragment() {
		clickLoginText()
		assertEquals(navController.currentDestination?.id, R.id.LoginFragment)
	}

	@Test
	fun onSignUp_emptyForm_showsError() {
		clickSignUpButton()

		onView(withId(R.id.signup_error_text_view)).check(matches(isDisplayed()))
	}

	@Test
	fun onSignUp_invalidEmail_showsEmailError() {
		insertInNameEditText("Vishal Gaur ")
		insertInMobileEditText("8976527465  ")
		insertInEmailEditText("  weuiyjyew.dciwe")
		insertInPwdEditText("dh239048fy")
		insertInCnfPwdEditText("dh239048fy")
		clickTermsSwitch()
		clickSignUpButton()

		onView(withId(R.id.signup_email_edit_text)).check(matches(hasErrorText(`is`(EMAIL_ERROR_TEXT))))
	}

	@Test
	fun onSignUp_invalidMobile_showsMobileError() {
		insertInNameEditText("Vishal Gaur ")
		insertInMobileEditText("86527465  ")
		insertInEmailEditText("  weuiyj@yew.dciwe")
		insertInPwdEditText("dh239048fy")
		insertInCnfPwdEditText("dh239048fy")
		clickTermsSwitch()
		clickSignUpButton()

		onView(withId(R.id.signup_mobile_edit_text)).check(matches(hasErrorText(`is`(MOB_ERROR_TEXT))))
	}

	@Test
	fun onSignUp_notAcceptedTerms_showsError() {
		insertInNameEditText("Vishal Gaur ")
		insertInMobileEditText("8652744565  ")
		insertInEmailEditText("  weuiyj@yew.dciwe")
		insertInPwdEditText("dh239048fy")
		insertInCnfPwdEditText("dh239048fy")
		clickSignUpButton()

		onView(withId(R.id.signup_error_text_view)).check(matches(withText("Accept the Terms.")))
	}

	@Test
	fun onSignUp_notSamePasswords_showsError() {
		insertInNameEditText("Vishal Gaur ")
		insertInMobileEditText("8652744565  ")
		insertInEmailEditText("  weuiyj@yew.dciwe")
		insertInPwdEditText("dh2398fy")
		insertInCnfPwdEditText("dh239048fy")
		clickTermsSwitch()
		clickSignUpButton()

		onView(withId(R.id.signup_error_text_view)).check(matches(withText("Both passwords are not same!")))
	}

	@Test
	fun onSignUp_validForm_showsNoError() {
		Intents.init()
		insertInNameEditText("Vishal Gaur ")
		insertInMobileEditText("8652744565  ")
		insertInEmailEditText("  weuiyj@yew.dciwe")
		insertInPwdEditText("dh2398fy")
		insertInCnfPwdEditText("dh2398fy")
		clickTermsSwitch()
		clickSignUpButton()

		intended(hasComponent(OtpActivity::class.java.name))
	}


	private fun insertInNameEditText(name: String) =
		onView(withId(R.id.signup_name_edit_text)).perform(scrollTo(), clearText(), typeText(name))

	private fun insertInMobileEditText(phone: String) =
		onView(withId(R.id.signup_mobile_edit_text)).perform(
			scrollTo(),
			clearText(),
			typeText(phone)
		)

	private fun insertInEmailEditText(email: String) =
		onView(withId(R.id.signup_email_edit_text)).perform(
			scrollTo(),
			clearText(),
			typeText(email)
		)

	private fun insertInPwdEditText(pwd: String) =
		onView(withId(R.id.signup_password_edit_text)).perform(
			scrollTo(),
			clearText(),
			typeText(pwd)
		)

	private fun insertInCnfPwdEditText(pwd2: String) =
		onView(withId(R.id.signup_cnf_password_edit_text)).perform(
			scrollTo(),
			clearText(),
			typeText(pwd2)
		)

	private fun clickTermsSwitch() =
		onView(withId(R.id.signup_policy_switch)).perform(scrollTo(), click())

	private fun clickSellerSwitch() =
		onView(withId(R.id.signup_seller_switch)).perform(scrollTo(), click())

	private fun clickSignUpButton() =
		onView(withId(R.id.signup_signup_btn)).perform(scrollTo(), click())

	private fun clickLoginText() =
		onView(withId(R.id.signup_login_text_view)).perform(
			scrollTo(),
			clickClickableSpan("Log In")
		)

}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/AddEditAddressViewModelTest.kt
================================================
package com.vishalgaur.shoppingapp.viewModels

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vishalgaur.shoppingapp.ServiceLocator
import com.vishalgaur.shoppingapp.ShoppingApplication
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.FakeAuthRepository
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepoInterface
import com.vishalgaur.shoppingapp.getOrAwaitValue
import com.vishalgaur.shoppingapp.ui.AddAddressViewErrors
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.Matchers.*
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi
class AddEditAddressViewModelTest {
	private lateinit var addEditAddressViewModel: AddEditAddressViewModel
	private lateinit var authRepository: AuthRepoInterface

	val user = UserData(
		"sdjm43892yfh948ehod",
		"Vishal",
		"+919999988888",
		"vishal@somemail.com",
		"dh94328hd",
		ArrayList(),
		ArrayList(),
		ArrayList()
	)

	@get:Rule
	var instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun setUp() {
		val context = ApplicationProvider.getApplicationContext<ShoppingApplication>()
		val sessionManager = ShoppingAppSessionManager(context)
		authRepository = FakeAuthRepository(sessionManager)
		authRepository.login(user, true)
		ServiceLocator.authRepository = authRepository
		addEditAddressViewModel = AddEditAddressViewModel(context)

	}

	@After
	fun cleanUp() = runBlockingTest {
		ServiceLocator.resetRepository()
	}

	@Test
	fun setIsEdit_setsValue() {
		runBlockingTest {
			addEditAddressViewModel.setIsEdit(false)
			val res = addEditAddressViewModel.isEdit.getOrAwaitValue()
			assertThat(res, `is`(false))
		}
	}

	@Test
	fun setAddressData_setsData() = runBlockingTest {
		val address = UserData.Address(
			"add-id-121",
			"adg",
			"shgd",
			"IN",
			"sfhg45eyh",
			"",
			"kanpuit",
			"up",
			"309890",
			"9999988558"
		)
		authRepository.insertAddress(address, user.userId)
		addEditAddressViewModel.setAddressData(address.addressId)
		val result = addEditAddressViewModel.addressData.getOrAwaitValue()
		assertThat(result, `is`(address))
	}

	@Test
	fun submitAddress_emptyForm_returnsError() {
		val fname = "adg"
		val lname = ""
		val code = "IN"
		val streetAdd = "sfhg45eyh"
		val streetAdd2 = ""
		val city = ""
		val state = "up"
		val zip = "309890"
		val phone = "9999988558"
		addEditAddressViewModel.submitAddress(
			code,
			fname,
			lname,
			streetAdd,
			streetAdd2,
			city,
			state,
			zip,
			phone
		)
		val result = addEditAddressViewModel.errorStatus.getOrAwaitValue()

		assertThat(result.size, `is`(greaterThan(0)))
		assertThat(result.contains(AddAddressViewErrors.ERR_CITY_EMPTY), `is`(true))
	}

	@Test
	fun submitAddress_invalidZipcode_returnsError() {
		val fname = "adg"
		val lname = "serdg"
		val code = "IN"
		val streetAdd = "sfhg45eyh"
		val streetAdd2 = ""
		val city = "sfhg"
		val state = "up"
		val zip = "30990"
		val phone = "9999988558"
		addEditAddressViewModel.submitAddress(
			code,
			fname,
			lname,
			streetAdd,
			streetAdd2,
			city,
			state,
			zip,
			phone
		)
		val result = addEditAddressViewModel.errorStatus.getOrAwaitValue()

		assertThat(result.size, `is`(greaterThan(0)))
		assertThat(result.contains(AddAddressViewErrors.ERR_ZIP_INVALID), `is`(true))
	}

	@Test
	fun submitAddress_invalidPhone_returnsError() {
		val fname = "adg"
		val lname = "serdg"
		val code = "IN"
		val streetAdd = "sfhg45eyh"
		val streetAdd2 = ""
		val city = "sfhg"
		val state = "up"
		val zip = "309903"
		val phone = "9999988efg558"
		addEditAddressViewModel.submitAddress(
			code,
			fname,
			lname,
			streetAdd,
			streetAdd2,
			city,
			state,
			zip,
			phone
		)
		val result = addEditAddressViewModel.errorStatus.getOrAwaitValue()

		assertThat(result.size, `is`(greaterThan(0)))
		assertThat(result.contains(AddAddressViewErrors.ERR_PHONE_INVALID), `is`(true))
	}

	@Test
	fun submitAddress_validData_returnsNoError() {
		val fname = "adg"
		val lname = "serdg"
		val code = "IN"
		val streetAdd = "sfhg45eyh"
		val streetAdd2 = ""
		val city = "sfhg"
		val state = "up"
		val zip = "302203"
		val phone = "9879988558"
		addEditAddressViewModel.submitAddress(
			code,
			fname,
			lname,
			streetAdd,
			streetAdd2,
			city,
			state,
			zip,
			phone
		)
		val result = addEditAddressViewModel.errorStatus.getOrAwaitValue()
		assertThat(result.size, `is`(0))

		val resData = addEditAddressViewModel.newAddressData.getOrAwaitValue()
		assertThat(resData, `is`(notNullValue()))
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/AddEditProductViewModelTest.kt
================================================
package com.vishalgaur.shoppingapp.viewModels

import android.net.Uri
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.core.net.toUri
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.getOrAwaitValue
import com.vishalgaur.shoppingapp.ui.AddProductViewErrors
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.notNullValue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AddEditProductViewModelTest {
	private lateinit var addEditProductViewModel: AddEditProductViewModel
	private lateinit var sessionManager: ShoppingAppSessionManager

	private val userSeller = UserData(
		"user1234selller",
		"Some Name",
		"+919999988888",
		"somemail123seller@somemail.com",
		"1234",
		emptyList(),
		emptyList(),
		emptyList(),
		"SELLER"
	)

	@get:Rule
	var instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun setUp() {
		sessionManager = ShoppingAppSessionManager(ApplicationProvider.getApplicationContext())
		sessionManager.createLoginSession(
			userSeller.userId,
			userSeller.name,
			userSeller.mobile,
			false,
			true
		)
		addEditProductViewModel =
			AddEditProductViewModel(ApplicationProvider.getApplicationContext())
	}

	@Test
	fun setCategory_Shoes() {
		addEditProductViewModel.setCategory("Shoes")
		val result = addEditProductViewModel.selectedCategory.getOrAwaitValue()
		assertThat(result, `is`("Shoes"))
	}

	@Test
	fun setIsEdit_true() {
		addEditProductViewModel.setIsEdit(true)
		val result = addEditProductViewModel.isEdit.getOrAwaitValue()
		assertThat(result, `is`(true))
	}

	@Test
	fun setIsEdit_false() {
		addEditProductViewModel.setIsEdit(false)
		val result = addEditProductViewModel.isEdit.getOrAwaitValue()
		assertThat(result, `is`(false))
	}


	@Test
	fun submitProduct_noData_returnsEmptyError() {
		addEditProductViewModel.setIsEdit(false)
		val name = ""
		val price = null
		val mrp = null
		val desc = ""
		val sizes = emptyList<Int>()
		val colors = emptyList<String>()
		val imgList = emptyList<Uri>()

		addEditProductViewModel.submitProduct(name, price, mrp, desc, sizes, colors, imgList)
		val result = addEditProductViewModel.errorStatus.getOrAwaitValue()

		assertThat(result, `is`(AddProductViewErrors.EMPTY))
	}

	@Test
	fun submitProduct_invalidPrice_returnsPriceError() {
		addEditProductViewModel.setIsEdit(false)
		val name = "vwsf"
		val mrp = 125.0
		val price = 0.0
		val desc = "crw rewg"
		val sizes = listOf(5, 6)
		val colors = listOf("red", "blue")
		val imgList = listOf("ffsd".toUri(), "sws".toUri())

		addEditProductViewModel.submitProduct(name, price, mrp, desc, sizes, colors, imgList)
		val result = addEditProductViewModel.errorStatus.getOrAwaitValue()

		assertThat(result, `is`(AddProductViewErrors.ERR_PRICE_0))
	}

	@Test
	fun submitProduct_allValid_returnsNoError() {
		addEditProductViewModel.setIsEdit(false)
		addEditProductViewModel.setCategory("Shoes")
		val name = "vwsf"
		val mrp = 125.0
		val price = 100.0
		val desc = "crw rewg"
		val sizes = listOf(5, 6)
		val colors = listOf("red", "blue")
		val imgList = listOf("ffsd".toUri(), "sws".toUri())

		addEditProductViewModel.submitProduct(name, price, mrp, desc, sizes, colors, imgList)
		val result = addEditProductViewModel.errorStatus.getOrAwaitValue()
		val resultPro = addEditProductViewModel.newProductData.getOrAwaitValue()

		assertThat(result, `is`(AddProductViewErrors.NONE))
		assertThat(resultPro, `is`(notNullValue()))
		assertThat(resultPro.name, `is`("vwsf"))
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/AuthViewModelTest.kt
================================================
package com.vishalgaur.shoppingapp.viewModels

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vishalgaur.shoppingapp.getOrAwaitValue
import com.vishalgaur.shoppingapp.ui.LoginViewErrors
import com.vishalgaur.shoppingapp.ui.SignUpViewErrors
import org.hamcrest.Matchers.*
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class AuthViewModelTest {
	private lateinit var authViewModel: AuthViewModel

	@get:Rule
	var instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun setUp() {
		authViewModel = AuthViewModel(ApplicationProvider.getApplicationContext())
	}

	@Test
	fun signUpSubmitData_noData_returnsEmptyError() {
		val name = ""
		val mobile = ""
		val email = ""
		val pwd1 = ""
		val pwd2 = ""
		val isAccepted = false
		val isSeller = false
		authViewModel.signUpSubmitData(name, mobile, email, pwd1, pwd2, isAccepted, isSeller)
		val result = authViewModel.errorStatus.getOrAwaitValue()

		assertThat(result, `is`(SignUpViewErrors.ERR_EMPTY))
		assertThat(authViewModel.userData.value, `is`(nullValue()))
	}

	@Test
	fun signUpSubmitData_notAccepted_returnsNotAccError() {
		val name = "uigbivs ihgfdsg"
		val mobile = "9988665555"
		val email = "owhfoi@oihw.cro"
		val pwd1 = "1234"
		val pwd2 = "1234"
		val isAccepted = false
		val isSeller = false
		authViewModel.signUpSubmitData(name, mobile, email, pwd1, pwd2, isAccepted, isSeller)
		val result = authViewModel.errorStatus.getOrAwaitValue()

		assertThat(result, `is`(SignUpViewErrors.ERR_NOT_ACC))
		assertThat(authViewModel.userData.value, `is`(nullValue()))
	}

	@Test
	fun signUpSubmitData_pwdNotEq_returnsPwdError() {
		val name = "uigbivs ihgfdsg"
		val mobile = "9988665555"
		val email = "owhfoi@oihw.cro"
		val pwd1 = "12345"
		val pwd2 = "1234"
		val isAccepted = false
		val isSeller = false
		authViewModel.signUpSubmitData(name, mobile, email, pwd1, pwd2, isAccepted, isSeller)
		val result = authViewModel.errorStatus.getOrAwaitValue()

		assertThat(result, `is`(SignUpViewErrors.ERR_PWD12NS))
		assertThat(authViewModel.userData.value, `is`(nullValue()))
	}

	@Test
	fun signUpSubmitData_invalidEmail_returnsEmailError() {
		val name = "uigbivs ihgfdsg"
		val mobile = "9988665555"
		val email = "owhfoi@oihwo"
		val pwd1 = "1234"
		val pwd2 = "1234"
		val isAccepted = true
		val isSeller = false
		authViewModel.signUpSubmitData(name, mobile, email, pwd1, pwd2, isAccepted, isSeller)
		val result = authViewModel.errorStatus.getOrAwaitValue()

		assertThat(result, `is`(SignUpViewErrors.ERR_EMAIL))
		assertThat(authViewModel.userData.value, `is`(nullValue()))
	}

	@Test
	fun signUpSubmitData_invalidMobile_returnsMobError() {
		val name = "uigbivs ihgfdsg"
		val mobile = "9988665fng55"
		val email = "owhfoi@oihw.coo"
		val pwd1 = "1234"
		val pwd2 = "1234"
		val isAccepted = true
		val isSeller = false
		authViewModel.signUpSubmitData(name, mobile, email, pwd1, pwd2, isAccepted, isSeller)
		val result = authViewModel.errorStatus.getOrAwaitValue()

		assertThat(result, `is`(SignUpViewErrors.ERR_MOBILE))
		assertThat(authViewModel.userData.value, `is`(nullValue()))
	}

	@Test
	fun signUpSubmitData_invalidEmailMobile_returnsEmailMobError() {
		val name = "uigbivs ihgfdsg"
		val mobile = "9988665fng55"
		val email = "owhfoi@oihwoo"
		val pwd1 = "1234"
		val pwd2 = "1234"
		val isAccepted = true
		val isSeller = false
		authViewModel.signUpSubmitData(name, mobile, email, pwd1, pwd2, isAccepted, isSeller)
		val result = authViewModel.errorStatus.getOrAwaitValue()

		assertThat(result, `is`(SignUpViewErrors.ERR_EMAIL_MOBILE))
		assertThat(authViewModel.userData.value, `is`(nullValue()))
	}

	@Test
	fun signUpSubmitData_validData_returnsNoError() {
		val name = "   uigbivs ihgfdsg"
		val mobile = "   9988665755"
		val email = "owhfoi@oihwoo.cwdo    "
		val pwd1 = "1234"
		val pwd2 = "1234"
		val isAccepted = true
		val isSeller = false
		authViewModel.signUpSubmitData(name, mobile, email, pwd1, pwd2, isAccepted, isSeller)
		val result = authViewModel.errorStatus.getOrAwaitValue()
		val dataRes = authViewModel.userData.getOrAwaitValue()
		assertThat(result, `is`(SignUpViewErrors.NONE))
		assertThat(dataRes, `is`(notNullValue()))
		assertThat(dataRes.name, `is`("uigbivs ihgfdsg"))
	}

	@Test
	fun loginSubmitData_noData_returnsEmptyError() {
		val mobile = ""
		val pwd = ""
		authViewModel.loginSubmitData(mobile, pwd)
		val result = authViewModel.errorStatusLoginFragment.getOrAwaitValue()

		assertThat(result, `is`(LoginViewErrors.ERR_EMPTY))
	}

	@Test
	fun loginSubmitData_invalidMobile_returnsMobileError() {
		val mobile = "9fwd988556699"
		val pwd = "123"
		authViewModel.loginSubmitData(mobile, pwd)
		val result = authViewModel.errorStatusLoginFragment.getOrAwaitValue()

		assertThat(result, `is`(LoginViewErrors.ERR_MOBILE))
	}

	@Test
	fun loginSubmitData_validData_returnsNoError() {
		val mobile = "9988556699"
		val pwd = "123"
		authViewModel.loginSubmitData(mobile, pwd)
		val result = authViewModel.errorStatusLoginFragment.getOrAwaitValue()

		assertThat(result, `is`(LoginViewErrors.NONE))
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/HomeViewModelTest.kt
================================================
package com.vishalgaur.shoppingapp.viewModels

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vishalgaur.shoppingapp.ServiceLocator
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.source.FakeAuthRepository
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepoInterface
import com.vishalgaur.shoppingapp.data.utils.StoreDataStatus
import com.vishalgaur.shoppingapp.getOrAwaitValue
import org.hamcrest.Matchers.`is`
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class HomeViewModelTest {
	private lateinit var homeViewModel: HomeViewModel
	private lateinit var authRepository: AuthRepoInterface

	@get:Rule
	var instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun setUp() {
		val sessionManager = ShoppingAppSessionManager(ApplicationProvider.getApplicationContext())
		authRepository = FakeAuthRepository(sessionManager)
		ServiceLocator.authRepository = authRepository
		homeViewModel = HomeViewModel(ApplicationProvider.getApplicationContext())
	}

	@After
	fun cleanUp() {
		ServiceLocator.resetRepository()
	}

    @Test
    fun setDataLoaded_setsValue() {
        homeViewModel.setDataLoaded()
        val result = homeViewModel.storeDataStatus.getOrAwaitValue()
        assertThat(result, `is`(StoreDataStatus.DONE))
    }

	@Test
	fun filterProducts_All() {
		homeViewModel.filterProducts("All")
		val result =  homeViewModel.filterCategory.getOrAwaitValue()
		assertThat(result, `is`("All"))
	}

	@Test
	fun filterProducts_Shoes() {
		homeViewModel.filterProducts("Shoes")
		val result =  homeViewModel.filterCategory.getOrAwaitValue()
		assertThat(result, `is`("Shoes"))
	}


}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/OrderViewModelTest.kt
================================================
package com.vishalgaur.shoppingapp.viewModels

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vishalgaur.shoppingapp.ServiceLocator
import com.vishalgaur.shoppingapp.ShoppingApplication
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.FakeAuthRepository
import com.vishalgaur.shoppingapp.data.source.FakeProductsRepository
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepoInterface
import com.vishalgaur.shoppingapp.data.source.repository.ProductsRepoInterface
import com.vishalgaur.shoppingapp.data.utils.StoreDataStatus
import com.vishalgaur.shoppingapp.getOrAwaitValue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.Matchers.*
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi
class OrderViewModelTest {

	private lateinit var orderViewModel: OrderViewModel
	private lateinit var productsRepository: ProductsRepoInterface
	private lateinit var authRepository: AuthRepoInterface

	val user = UserData(
		"sdjm43892yfh948ehod",
		"Vishal",
		"+919999988888",
		"vishal@somemail.com",
		"dh94328hd",
		ArrayList(),
		ArrayList(),
		ArrayList()
	)

	val address = UserData.Address(
		"add-id-121",
		"adg",
		"shgd",
		"IN",
		"sfhg45eyh",
		"",
		"kanpuit",
		"up",
		"309890",
		"9999988558"
	)

	val item1 = UserData.CartItem(
		"item2123",
		"pro123",
		"owner23",
		1,
		"Red",
		10
	)

	val item2 = UserData.CartItem(
		"item2123456347",
		"pro12345",
		"owner23",
		1,
		"Blue",
		9
	)

	@get:Rule
	var instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun setUp() {
		productsRepository = FakeProductsRepository()
		val context = ApplicationProvider.getApplicationContext<ShoppingApplication>()
		val sessionManager = ShoppingAppSessionManager(context)
		authRepository = FakeAuthRepository(sessionManager)
		authRepository.login(user, true)
		ServiceLocator.productsRepository = productsRepository
		ServiceLocator.authRepository = authRepository

		orderViewModel = OrderViewModel(context)
	}

	@After
	fun cleanUp() = runBlockingTest {
		ServiceLocator.resetRepository()
	}

	@Test
	fun getCartItems_loadsData() = runBlocking {
		orderViewModel.getCartItems()
		delay(200)
		val result = orderViewModel.dataStatus.getOrAwaitValue()
		assertThat(result, `is`(StoreDataStatus.DONE))
	}

	@Test
	fun getAddresses_noAddress_loadsData() = runBlocking {
		orderViewModel.getUserAddresses()
		delay(200)
		val result = orderViewModel.dataStatus.getOrAwaitValue()
		assertThat(result, `is`(StoreDataStatus.DONE))
		val resAdd = orderViewModel.userAddresses.getOrAwaitValue()
		assertThat(resAdd.size, `is`(0))
	}

	@Test
	fun getAddresses_hasAddress_loadsData() = runBlocking {
		authRepository.insertAddress(address, user.userId)
		delay(100)
		orderViewModel.getUserAddresses()
		delay(100)
		val result = orderViewModel.dataStatus.getOrAwaitValue()
		assertThat(result, `is`(StoreDataStatus.DONE))
		val resAdd = orderViewModel.userAddresses.getOrAwaitValue()
		assertThat(resAdd.size, `is`(1))
	}

	@Test
	fun deleteAddress_deletesAddress() = runBlocking{
		authRepository.insertAddress(address, user.userId)
		delay(100)
		orderViewModel.getUserAddresses()
		delay(100)
		val resAdd = orderViewModel.userAddresses.getOrAwaitValue()
		assertThat(resAdd.size, `is`(1))

		orderViewModel.deleteAddress(address.addressId)
		delay(100)
		val resAdd2 = orderViewModel.userAddresses.getOrAwaitValue()
		assertThat(resAdd2.size, `is`(0))
	}

	@Test
	fun getItemsPriceTotal_returnsTotal() {
		runBlocking {
			authRepository.insertCartItemByUserId(item1, user.userId)
			delay(100)
			val result = orderViewModel.getItemsPriceTotal()
			assertThat(result, `is`(0.0))
		}
	}

	@Test
	fun toggleLike() {
		runBlocking {
			val res1 = orderViewModel.userLikes.getOrAwaitValue()
			orderViewModel.toggleLikeProduct("pro-if2r3")
			delay(100)
			val res2 = orderViewModel.userLikes.getOrAwaitValue()
			assertThat(res1.size, not(res2.size))
		}
	}

	@Test
	fun setQuantity_setsQuantity() = runBlocking {
		authRepository.insertCartItemByUserId(item1, user.userId)
		delay(100)
		val res1 = orderViewModel.cartItems.getOrAwaitValue().find { it.itemId == item1.itemId }
		val size1 = res1?.quantity ?: 0
		orderViewModel.setQuantityOfItem(item1.itemId, 1)
		delay(100)
		val res2 = orderViewModel.cartItems.getOrAwaitValue().find { it.itemId == item1.itemId }
		val size2 = res2?.quantity ?: 0

		assertThat(size1, `is`(1))
		assertThat(size2, `is`(2))
	}

	@Test
	fun deleteItemFromCart_deletesItem() = runBlocking {
		authRepository.insertCartItemByUserId(item1, user.userId)
		delay(100)
		val res1 = orderViewModel.cartItems.getOrAwaitValue().find { it.itemId == item1.itemId }
		assertThat(res1, `is`(notNullValue()))

		orderViewModel.deleteItemFromCart(item1.itemId)
		delay(100)
		val res2 = orderViewModel.cartItems.getOrAwaitValue().find { it.itemId == item1.itemId }
		assertThat(res2, `is`(nullValue()))
	}
}

================================================
FILE: app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/ProductViewModelTest.kt
================================================
package com.vishalgaur.shoppingapp.viewModels

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vishalgaur.shoppingapp.ServiceLocator
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.FakeAuthRepository
import com.vishalgaur.shoppingapp.data.source.FakeProductsRepository
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepoInterface
import com.vishalgaur.shoppingapp.data.source.repository.ProductsRepoInterface
import com.vishalgaur.shoppingapp.getOrAwaitValue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runBlockingTest
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.not
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi
class ProductViewModelTest {
	private lateinit var productViewModel: ProductViewModel
	private lateinit var productId: String
	private lateinit var productsRepository: ProductsRepoInterface
	private lateinit var authRepository: AuthRepoInterface

	val user = UserData(
		"sdjm43892yfh948ehod",
		"Vishal",
		"+919999988888",
		"vishal@somemail.com",
		"dh94328hd",
		ArrayList(),
		ArrayList(),
		ArrayList()
	)

	@get:Rule
	var instantTaskExecutorRule = InstantTaskExecutorRule()

	@Before
	fun setUp() {
		productsRepository = FakeProductsRepository()
		val sessionManager = ShoppingAppSessionManager(ApplicationProvider.getApplicationContext())
		authRepository = FakeAuthRepository(sessionManager)
		authRepository.login(user, true)
		ServiceLocator.productsRepository = productsRepository
		ServiceLocator.authRepository = authRepository
		productId = "pro-shoes-wofwopjf-1"
		productViewModel = ProductViewModel(productId, ApplicationProvider.getApplicationContext())
	}

	@After
	fun cleanUp() = runBlockingTest {
		ServiceLocator.resetRepository()
	}

	@Test
	fun toggleLikeProduct_false_true() {
		val result1 = productViewModel.isLiked.value
		runBlocking {
			productViewModel.toggleLikeProduct()
			delay(1000)
			val result2 = productViewModel.isLiked.getOrAwaitValue()
			assertThat(result1, not(result2))
		}

	}
}

================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.vishalgaur.shoppingapp">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".ShoppingApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ShoppingApp">

        <activity
            android:name=".ui.LaunchActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".ui.loginSignup.LoginSignupActivity" />

        <activity
            android:name=".ui.home.MainActivity"
            android:exported="true"
            android:launchMode="singleTop" />

        <activity
            android:name=".ui.loginSignup.OtpActivity" />

        <meta-data
            android:name="preloaded_fonts"
            android:resource="@array/preloaded_fonts" />
    </application>

</manifest>

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/ServiceLocator.kt
================================================
package com.vishalgaur.shoppingapp

import android.content.Context
import androidx.annotation.VisibleForTesting
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.source.ProductDataSource
import com.vishalgaur.shoppingapp.data.source.UserDataSource
import com.vishalgaur.shoppingapp.data.source.local.ProductsLocalDataSource
import com.vishalgaur.shoppingapp.data.source.local.ShoppingAppDatabase
import com.vishalgaur.shoppingapp.data.source.local.UserLocalDataSource
import com.vishalgaur.shoppingapp.data.source.remote.AuthRemoteDataSource
import com.vishalgaur.shoppingapp.data.source.remote.ProductsRemoteDataSource
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepoInterface
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepository
import com.vishalgaur.shoppingapp.data.source.repository.ProductsRepoInterface
import com.vishalgaur.shoppingapp.data.source.repository.ProductsRepository

object ServiceLocator {
	private var database: ShoppingAppDatabase? = null
	private val lock = Any()

	@Volatile
	var authRepository: AuthRepoInterface? = null
		@VisibleForTesting set

	@Volatile
	var productsRepository: ProductsRepoInterface? = null
		@VisibleForTesting set

	fun provideAuthRepository(context: Context): AuthRepoInterface {
		synchronized(this) {
			return authRepository ?: createAuthRepository(context)
		}
	}

	fun provideProductsRepository(context: Context): ProductsRepoInterface {
		synchronized(this) {
			return productsRepository ?: createProductsRepository(context)
		}
	}

	@VisibleForTesting
	fun resetRepository() {
		synchronized(lock) {
			database?.apply {
				clearAllTables()
				close()
			}
			database = null
			authRepository = null
		}
	}

	private fun createProductsRepository(context: Context): ProductsRepoInterface {
		val newRepo =
			ProductsRepository(ProductsRemoteDataSource(), createProductsLocalDataSource(context))
		productsRepository = newRepo
		return newRepo
	}

	private fun createAuthRepository(context: Context): AuthRepoInterface {
		val appSession = ShoppingAppSessionManager(context.applicationContext)
		val newRepo =
			AuthRepository(createUserLocalDataSource(context), AuthRemoteDataSource(), appSession)
		authRepository = newRepo
		return newRepo
	}

	private fun createProductsLocalDataSource(context: Context): ProductDataSource {
		val database = database ?: ShoppingAppDatabase.getInstance(context.applicationContext)
		return ProductsLocalDataSource(database.productsDao())
	}

	private fun createUserLocalDataSource(context: Context): UserDataSource {
		val database = database ?: ShoppingAppDatabase.getInstance(context.applicationContext)
		return UserLocalDataSource(database.userDao())
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/ShoppingApplication.kt
================================================
package com.vishalgaur.shoppingapp

import android.app.Application
import com.vishalgaur.shoppingapp.data.source.repository.AuthRepoInterface
import com.vishalgaur.shoppingapp.data.source.repository.ProductsRepoInterface

class ShoppingApplication : Application() {
	val authRepository: AuthRepoInterface
		get() = ServiceLocator.provideAuthRepository(this)

	val productsRepository: ProductsRepoInterface
		get() = ServiceLocator.provideProductsRepository(this)

	override fun onCreate() {
		super.onCreate()
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/Utils.kt
================================================
package com.vishalgaur.shoppingapp

import java.util.*
import java.util.regex.Pattern
import kotlin.math.roundToInt

const val MOB_ERROR_TEXT = "Enter valid mobile number!"
const val EMAIL_ERROR_TEXT = "Enter valid email address!"
const val ERR_INIT = "ERROR"
const val ERR_EMAIL = "_EMAIL"
const val ERR_MOBILE = "_MOBILE"
const val ERR_UPLOAD = "UploadErrorException"

internal fun isEmailValid(email: String): Boolean {
	val EMAIL_PATTERN = Pattern.compile(
		"\\s*[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
				"\\@" +
				"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
				"(" +
				"\\." +
				"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
				")+\\s*"
	)
	return if (email.isEmpty()) {
		false
	} else {
		EMAIL_PATTERN.matcher(email).matches()
	}
}

internal fun isPhoneValid(phone: String): Boolean {
	val PHONE_PATTERN = Pattern.compile("^\\s*[6-9]\\d{9}\\s*\$")
	return if (phone.isEmpty()) {
		false
	} else {
		PHONE_PATTERN.matcher(phone).matches()
	}
}

internal fun isZipCodeValid(zipCode: String): Boolean {
	val ZIPCODE_PATTERN = Pattern.compile("^\\s*[1-9]\\d{5}\\s*\$")
	return if (zipCode.isEmpty()) {
		false
	} else {
		ZIPCODE_PATTERN.matcher(zipCode).matches()
	}
}

internal fun getRandomString(length: Int, uNum: String, endLength: Int): String {
	val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
	fun getStr(l: Int): String = (1..l).map { allowedChars.random() }.joinToString("")
	return getStr(length) + uNum + getStr(endLength)
}

internal fun getProductId(ownerId: String, proCategory: String): String {
	val uniqueId = UUID.randomUUID().toString()
	return "pro-$proCategory-$ownerId-$uniqueId"
}

internal fun getOfferPercentage(costPrice: Double, sellingPrice: Double): Int {
	if (costPrice == 0.0 || sellingPrice == 0.0 || costPrice <= sellingPrice)
		return 0
	val off = ((costPrice - sellingPrice) * 100) / costPrice
	return off.roundToInt()
}

internal fun getAddressId(userId: String): String {
	val uniqueId = UUID.randomUUID().toString()
	return "$userId-$uniqueId"
}

internal fun shouldBypassOTPValidation() : Boolean {
	return false
}


================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/Product.kt
================================================
package com.vishalgaur.shoppingapp.data

import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize

@Parcelize
@Entity(tableName = "products")
data class Product @JvmOverloads constructor(
	@PrimaryKey
	var productId: String = "",
	var name: String = "",
	var owner: String = "",
	var description: String = "",
	var category: String = "",
	var price: Double = 0.0,
	var mrp: Double = 0.0,
	var availableSizes: List<Int> = ArrayList(),
	var availableColors: List<String> = ArrayList(),
	var images: List<String> = ArrayList(),
	var rating: Double = 0.0
) : Parcelable {
	fun toHashMap(): HashMap<String, Any> {
		return hashMapOf(
			"productId" to productId,
			"name" to name,
			"owner" to owner,
			"description" to description,
			"category" to category,
			"price" to price,
			"mrp" to mrp,
			"availableSizes" to availableSizes,
			"availableColors" to availableColors,
			"images" to images,
			"rating" to rating
		)
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/Result.kt
================================================
package com.vishalgaur.shoppingapp.data

import com.vishalgaur.shoppingapp.data.Result.Success

/**
 * A generic class that holds a value with its loading status.
 * @param <T>
 */
sealed class Result<out R> {

	data class Success<out T>(val data: T) : Result<T>()
	data class Error(val exception: Exception) : Result<Nothing>()
	object Loading : Result<Nothing>()

	override fun toString(): String {
		return when (this) {
			is Success<*> -> "Success[data=$data]"
			is Error -> "Error[exception=$exception]"
			Loading -> "Loading"
		}
	}
}

/**
 * `true` if [Result] is of type [Success] & holds non-null [Success.data].
 */
val Result<*>.succeeded
	get() = this is Success && data != null

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/ShoppingAppSessionManager.kt
================================================
package com.vishalgaur.shoppingapp.data

import android.content.Context
import android.content.SharedPreferences

class ShoppingAppSessionManager(context: Context) {

	var userSession: SharedPreferences =
		context.getSharedPreferences("userSessionData", Context.MODE_PRIVATE)
	var editor: SharedPreferences.Editor = userSession.edit()


	fun createLoginSession(
		id: String,
		name: String,
		mobile: String,
		isRemOn: Boolean,
		isSeller: Boolean
	) {
		editor.putBoolean(IS_LOGIN, true)
		editor.putString(KEY_ID, id)
		editor.putString(KEY_NAME, name)
		editor.putString(KEY_MOBILE, mobile)
		editor.putBoolean(KEY_REMEMBER_ME, isRemOn)
		editor.putBoolean(KEY_IS_SELLER, isSeller)

		editor.commit()
	}

	fun isUserSeller(): Boolean = userSession.getBoolean(KEY_IS_SELLER, false)

	fun isRememberMeOn(): Boolean = userSession.getBoolean(KEY_REMEMBER_ME, false)

	fun getPhoneNumber(): String? = userSession.getString(KEY_MOBILE, null)

	fun getUserDataFromSession(): HashMap<String, String?> {
		return hashMapOf(
			KEY_ID to userSession.getString(KEY_ID, null),
			KEY_NAME to userSession.getString(KEY_NAME, null),
			KEY_MOBILE to userSession.getString(KEY_MOBILE, null)
		)
	}

	fun getUserIdFromSession(): String? = userSession.getString(KEY_ID, null)

	fun isLoggedIn(): Boolean = userSession.getBoolean(IS_LOGIN, false)

	fun logoutFromSession() {
		editor.clear()
		editor.commit()
	}

	companion object {
		private const val IS_LOGIN = "isLoggedIn"
		private const val KEY_NAME = "userName"
		private const val KEY_MOBILE = "userMobile"
		private const val KEY_ID = "userId"
		private const val KEY_REMEMBER_ME = "isRemOn"
		private const val KEY_IS_SELLER = "isSeller"
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/UserData.kt
================================================
package com.vishalgaur.shoppingapp.data

import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import com.vishalgaur.shoppingapp.data.utils.ObjectListTypeConvertor
import com.vishalgaur.shoppingapp.data.utils.OrderStatus
import com.vishalgaur.shoppingapp.data.utils.UserType
import kotlinx.parcelize.Parcelize
import java.util.*
import kotlin.collections.ArrayList

@Parcelize
@Entity(tableName = "users")
data class UserData(
	@PrimaryKey
	var userId: String = "",
	var name: String = "",
	var mobile: String = "",
	var email: String = "",
	var password: String = "",
	var likes: List<String> = ArrayList(),
	@TypeConverters(ObjectListTypeConvertor::class)
	var addresses: List<Address> = ArrayList(),
	@TypeConverters(ObjectListTypeConvertor::class)
	var cart: List<CartItem> = ArrayList(),
	@TypeConverters(ObjectListTypeConvertor::class)
	var orders: List<OrderItem> = ArrayList(),
	var userType: String = UserType.CUSTOMER.name
) : Parcelable {
	fun toHashMap(): HashMap<String, Any> {
		return hashMapOf(
			"userId" to userId,
			"name" to name,
			"email" to email,
			"mobile" to mobile,
			"password" to password,
			"likes" to likes,
			"addresses" to addresses.map { it.toHashMap() },
			"userType" to userType
		)
	}

	@Parcelize
	data class OrderItem(
		var orderId: String = "",
		var customerId: String = "",
		var items: List<CartItem> = ArrayList(),
		var itemsPrices: Map<String, Double> = mapOf(),
		var deliveryAddress: Address = Address(),
		var shippingCharges: Double = 0.0,
		var paymentMethod: String = "",
		var orderDate: Date = Date(),
		var status: String = OrderStatus.CONFIRMED.name
	) : Parcelable {
		fun toHashMap(): HashMap<String, Any> {
			return hashMapOf(
				"orderId" to orderId,
				"customerId" to customerId,
				"items" to items.map { it.toHashMap() },
				"itemsPrices" to itemsPrices,
				"deliveryAddress" to deliveryAddress.toHashMap(),
				"shippingCharges" to shippingCharges,
				"paymentMethod" to paymentMethod,
				"orderDate" to orderDate,
				"status" to status
			)
		}
	}

	@Parcelize
	data class Address(
		var addressId: String = "",
		var fName: String = "",
		var lName: String = "",
		var countryISOCode: String = "",
		var streetAddress: String = "",
		var streetAddress2: String = "",
		var city: String = "",
		var state: String = "",
		var zipCode: String = "",
		var phoneNumber: String = ""
	) : Parcelable {
		fun toHashMap(): HashMap<String, String> {
			return hashMapOf(
				"addressId" to addressId,
				"fName" to fName,
				"lName" to lName,
				"countryISOCode" to countryISOCode,
				"streetAddress" to streetAddress,
				"streetAddress2" to streetAddress2,
				"city" to city,
				"state" to state,
				"zipCode" to zipCode,
				"phoneNumber" to phoneNumber
			)
		}
	}

	@Parcelize
	data class CartItem(
		var itemId: String = "",
		var productId: String = "",
		var ownerId: String = "",
		var quantity: Int = 0,
		var color: String?,
		var size: Int?
	) : Parcelable {
		constructor() : this("", "", "", 0, "NA", -1)

		fun toHashMap(): HashMap<String, Any> {
			val hashMap = hashMapOf<String, Any>()
			hashMap["itemId"] = itemId
			hashMap["productId"] = productId
			hashMap["ownerId"] = ownerId
			hashMap["quantity"] = quantity
			if (color != null)
				hashMap["color"] = color!!
			if (size != null)
				hashMap["size"] = size!!
			return hashMap
		}
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/ProductDataSource.kt
================================================
package com.vishalgaur.shoppingapp.data.source

import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.Result

interface ProductDataSource {

	fun observeProducts(): LiveData<Result<List<Product>>?>

	suspend fun getAllProducts(): Result<List<Product>>

	suspend fun refreshProducts() {}

	suspend fun getProductById(productId: String): Result<Product>

	suspend fun insertProduct(newProduct: Product)

	suspend fun updateProduct(proData: Product)

	fun observeProductsByOwner(ownerId: String): LiveData<Result<List<Product>>?> {
		return MutableLiveData()
	}

	suspend fun getAllProductsByOwner(ownerId: String): Result<List<Product>> {
		return Result.Success(emptyList())
	}

	suspend fun uploadImage(uri: Uri, fileName: String): Uri? {
		return null
	}

	fun revertUpload(fileName: String) {}
	fun deleteImage(imgUrl: String) {}
	suspend fun deleteProduct(productId: String)
	suspend fun deleteAllProducts() {}
	suspend fun insertMultipleProducts(data: List<Product>) {}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/UserDataSource.kt
================================================
package com.vishalgaur.shoppingapp.data.source

import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.utils.EmailMobileData

interface UserDataSource {
	suspend fun addUser(userData: UserData)

	suspend fun getUserById(userId: String): Result<UserData?>

	fun updateEmailsAndMobiles(email: String, mobile: String) {}

	suspend fun getEmailsAndMobiles(): EmailMobileData? {
		return null
	}

	suspend fun getUserByMobileAndPassword(
		mobile: String,
		password: String
	): MutableList<UserData> {
		return mutableListOf()
	}

	suspend fun likeProduct(productId: String, userId: String) {}

	suspend fun dislikeProduct(productId: String, userId: String) {}

	suspend fun insertAddress(newAddress: UserData.Address, userId: String) {}

	suspend fun updateAddress(newAddress: UserData.Address, userId: String) {}

	suspend fun deleteAddress(addressId: String, userId: String) {}

	suspend fun insertCartItem(newItem: UserData.CartItem, userId: String) {}

	suspend fun updateCartItem(item: UserData.CartItem, userId: String) {}

	suspend fun deleteCartItem(itemId: String, userId: String) {}

	suspend fun placeOrder(newOrder: UserData.OrderItem, userId: String) {}

	suspend fun setStatusOfOrderByUserId(orderId: String, userId: String, status: String) {}

	suspend fun clearUser() {}

	suspend fun getUserByMobile(phoneNumber: String): UserData? {
		return null
	}

	suspend fun getOrdersByUserId(userId: String): Result<List<UserData.OrderItem>?>

	suspend fun getAddressesByUserId(userId: String): Result<List<UserData.Address>?>

	suspend fun getLikesByUserId(userId: String): Result<List<String>?>
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/ProductsDao.kt
================================================
package com.vishalgaur.shoppingapp.data.source.local

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.vishalgaur.shoppingapp.data.Product

@Dao
interface ProductsDao {
	@Insert(onConflict = OnConflictStrategy.REPLACE)
	suspend fun insert(product: Product)

	@Insert(onConflict = OnConflictStrategy.REPLACE)
	suspend fun insertListOfProducts(products: List<Product>)

	@Query("SELECT * FROM products")
	suspend fun getAllProducts(): List<Product>

	@Query("SELECT * FROM products")
	fun observeProducts(): LiveData<List<Product>>

	@Query("SELECT * FROM products WHERE owner = :ownerId")
	fun observeProductsByOwner(ownerId: String): LiveData<List<Product>>

	@Query("SELECT * FROM products WHERE productId = :proId")
	suspend fun getProductById(proId: String): Product?

	@Query("SELECT * FROM products WHERE owner = :ownerId")
	suspend fun getProductsByOwnerId(ownerId: String): List<Product>

	@Query("DELETE FROM products WHERE productId = :proId")
	suspend fun deleteProductById(proId: String): Int

	@Query("DELETE FROM products")
	suspend fun deleteAllProducts()
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/ProductsLocalDataSource.kt
================================================
package com.vishalgaur.shoppingapp.data.source.local

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.Result.*
import com.vishalgaur.shoppingapp.data.source.ProductDataSource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class ProductsLocalDataSource internal constructor(
	private val productsDao: ProductsDao,
	private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : ProductDataSource {
	override fun observeProducts(): LiveData<Result<List<Product>>?> {
		return try {
			Transformations.map(productsDao.observeProducts()) {
				Success(it)
			}
		} catch (e: Exception) {
			Transformations.map(MutableLiveData(e)) {
				Error(e)
			}
		}
	}

	override fun observeProductsByOwner(ownerId: String): LiveData<Result<List<Product>>?> {
		return try {
			Transformations.map(productsDao.observeProductsByOwner(ownerId)) {
				Success(it)
			}
		} catch (e: Exception) {
			Transformations.map(MutableLiveData(e)) {
				Error(e)
			}
		}
	}

	override suspend fun getAllProducts(): Result<List<Product>> = withContext(ioDispatcher) {
		return@withContext try {
			Success(productsDao.getAllProducts())
		} catch (e: Exception) {
			Error(e)
		}
	}

	override suspend fun getAllProductsByOwner(ownerId: String): Result<List<Product>> =
		withContext(ioDispatcher) {
			return@withContext try {
				Success(productsDao.getProductsByOwnerId(ownerId))
			} catch (e: Exception) {
				Error(e)
			}
		}

	override suspend fun getProductById(productId: String): Result<Product> =
		withContext(ioDispatcher) {
			try {
				val product = productsDao.getProductById(productId)
				if (product != null) {
					return@withContext Success(product)
				} else {
					return@withContext Error(Exception("Product Not Found!"))
				}
			} catch (e: Exception) {
				return@withContext Error(e)
			}
		}

	override suspend fun insertProduct(newProduct: Product) = withContext(ioDispatcher) {
		productsDao.insert(newProduct)
	}

	override suspend fun updateProduct(proData: Product) = withContext(ioDispatcher) {
		productsDao.insert(proData)
	}

	override suspend fun insertMultipleProducts(data: List<Product>) = withContext(ioDispatcher) {
		productsDao.insertListOfProducts(data)
	}

	override suspend fun deleteProduct(productId: String): Unit = withContext(ioDispatcher) {
		productsDao.deleteProductById(productId)
	}

	override suspend fun deleteAllProducts() = withContext(ioDispatcher) {
		productsDao.deleteAllProducts()
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/ShoppingAppDatabase.kt
================================================
package com.vishalgaur.shoppingapp.data.source.local

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.utils.DateTypeConvertors
import com.vishalgaur.shoppingapp.data.utils.ListTypeConverter
import com.vishalgaur.shoppingapp.data.utils.ObjectListTypeConvertor

@Database(entities = [UserData::class, Product::class], version = 2)
@TypeConverters(ListTypeConverter::class, ObjectListTypeConvertor::class, DateTypeConvertors::class)
abstract class ShoppingAppDatabase : RoomDatabase() {
	abstract fun userDao(): UserDao
	abstract fun productsDao(): ProductsDao

	companion object {
		@Volatile
		private var INSTANCE: ShoppingAppDatabase? = null

		fun getInstance(context: Context): ShoppingAppDatabase =
			INSTANCE ?: synchronized(this) {
				INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
			}

		private fun buildDatabase(context: Context) =
			Room.databaseBuilder(
				context.applicationContext,
				ShoppingAppDatabase::class.java, "ShoppingAppDb"
			)
				.fallbackToDestructiveMigration()
				.allowMainThreadQueries()
				.build()
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/UserDao.kt
================================================
package com.vishalgaur.shoppingapp.data.source.local

import androidx.room.*
import com.vishalgaur.shoppingapp.data.UserData

@Dao
interface UserDao {
	@Insert(onConflict = OnConflictStrategy.REPLACE)
	suspend fun insert(uData: UserData)

	@Query("SELECT * FROM users WHERE userId = :userId")
	suspend fun getById(userId: String): UserData?

	@Query("SELECT * FROM users WHERE mobile = :mobile")
	suspend fun getByMobile(mobile: String): UserData?

	@Update(entity = UserData::class)
	suspend fun updateUser(obj: UserData)

	@Query("DELETE FROM users")
	suspend fun clear()
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/UserLocalDataSource.kt
================================================
package com.vishalgaur.shoppingapp.data.source.local

import android.util.Log
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.Result.*
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.UserDataSource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class UserLocalDataSource internal constructor(
	private val userDao: UserDao,
	private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : UserDataSource {

	override suspend fun addUser(userData: UserData) {
		withContext(ioDispatcher) {
			userDao.clear()
			userDao.insert(userData)
		}
	}

	override suspend fun getUserById(userId: String): Result<UserData?> =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					return@withContext Success(uData)
				} else {
					return@withContext Error(Exception("User Not Found!"))
				}
			} catch (e: Exception) {
				return@withContext Error(e)
			}
		}

	override suspend fun getUserByMobile(phoneNumber: String): UserData? =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getByMobile(phoneNumber)
				if (uData != null) {
					return@withContext uData
				} else {
					return@withContext null
				}
			} catch (e: Exception) {
				Log.d("UserLocalSource", "onGetUser: Error Occurred, $e")
				return@withContext null
			}
		}

	override suspend fun getOrdersByUserId(userId: String): Result<List<UserData.OrderItem>?> =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					val ordersList = uData.orders
					return@withContext Success(ordersList)
				} else {
					return@withContext Error(Exception("User Not Found"))
				}

			} catch (e: Exception) {
				Log.d("UserLocalSource", "onGetOrders: Error Occurred, ${e.message}")
				return@withContext Error(e)
			}
		}

	override suspend fun getAddressesByUserId(userId: String): Result<List<UserData.Address>?> =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					val addressList = uData.addresses
					return@withContext Success(addressList)
				} else {
					return@withContext Error(Exception("User Not Found"))
				}

			} catch (e: Exception) {
				Log.d("UserLocalSource", "onGetAddress: Error Occurred, ${e.message}")
				return@withContext Error(e)
			}
		}

	override suspend fun getLikesByUserId(userId: String): Result<List<String>?> =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					val likesList = uData.likes
					return@withContext Success(likesList)
				} else {
					return@withContext Error(Exception("User Not Found"))
				}

			} catch (e: Exception) {
				Log.d("UserLocalSource", "onGetLikes: Error Occurred, ${e.message}")
				return@withContext Error(e)
			}
		}

	override suspend fun dislikeProduct(productId: String, userId: String) =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					val likesList = uData.likes.toMutableList()
					likesList.remove(productId)
					uData.likes = likesList
					userDao.updateUser(uData)
				} else {
					throw Exception("User Not Found")
				}
			} catch (e: Exception) {
				Log.d("UserLocalSource", "onGetLikes: Error Occurred, ${e.message}")
				throw e
			}
		}

	override suspend fun likeProduct(productId: String, userId: String) =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					val likesList = uData.likes.toMutableList()
					likesList.add(productId)
					uData.likes = likesList
					userDao.updateUser(uData)
				} else {
					throw Exception("User Not Found")
				}
			} catch (e: Exception) {
				Log.d("UserLocalSource", "onGetLikes: Error Occurred, ${e.message}")
				throw e
			}
		}

	override suspend fun insertCartItem(newItem: UserData.CartItem, userId: String) =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					val cartItems = uData.cart.toMutableList()
					cartItems.add(newItem)
					uData.cart = cartItems
					userDao.updateUser(uData)
				} else {
					throw Exception("User Not Found")
				}
			} catch (e: Exception) {
				Log.d("UserLocalSource", "onInsertCartItem: Error Occurred, ${e.message}")
				throw e
			}
		}

	override suspend fun updateCartItem(item: UserData.CartItem, userId: String) =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					val cartItems = uData.cart.toMutableList()
					val pos = cartItems.indexOfFirst { it.itemId == item.itemId }
					if (pos >= 0) {
						cartItems[pos] = item
					}
					uData.cart = cartItems
					userDao.updateUser(uData)
				} else {
					throw Exception("User Not Found")
				}
			} catch (e: Exception) {
				Log.d("UserLocalSource", "onInsertCartItem: Error Occurred, ${e.message}")
				throw e
			}
		}

	override suspend fun deleteCartItem(itemId: String, userId: String) =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					val cartItems = uData.cart.toMutableList()
					val pos = cartItems.indexOfFirst { it.itemId == itemId }
					if (pos >= 0) {
						cartItems.removeAt(pos)
					}
					uData.cart = cartItems
					userDao.updateUser(uData)
				} else {
					throw Exception("User Not Found")
				}
			} catch (e: Exception) {
				Log.d("UserLocalSource", "onInsertCartItem: Error Occurred, ${e.message}")
				throw e
			}
		}

	override suspend fun setStatusOfOrderByUserId(orderId: String, userId: String, status: String) =
		withContext(ioDispatcher) {
			try {
				val uData = userDao.getById(userId)
				if (uData != null) {
					val orders = uData.orders.toMutableList()
					val pos = orders.indexOfFirst { it.orderId == orderId }
					if (pos >= 0) {
						orders[pos].status = status
						val custId = orders[pos].customerId
						val custData = userDao.getById(custId)
						if (custData != null) {
							val orderList = custData.orders.toMutableList()
							val idx = orderList.indexOfFirst { it.orderId == orderId }
							if (idx >= 0) {
								orderList[idx].status = status
							}
							custData.orders = orderList
							userDao.updateUser(custData)
						}
					}
					uData.orders = orders
					userDao.updateUser(uData)
				} else {
					throw Exception("User Not Found")
				}
			} catch (e: Exception) {
				Log.d("UserLocalSource", "onInsertCartItem: Error Occurred, ${e.message}")
				throw e
			}
		}

	override suspend fun clearUser() {
		withContext(ioDispatcher) {
			userDao.clear()
		}
	}

}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/remote/AuthRemoteDataSource.kt
================================================
package com.vishalgaur.shoppingapp.data.source.remote

import android.util.Log
import com.google.firebase.firestore.FieldValue
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.Result.Error
import com.vishalgaur.shoppingapp.data.Result.Success
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.UserDataSource
import com.vishalgaur.shoppingapp.data.utils.EmailMobileData
import com.vishalgaur.shoppingapp.data.utils.OrderStatus
import kotlinx.coroutines.tasks.await

class AuthRemoteDataSource : UserDataSource {
	private val firebaseDb: FirebaseFirestore = Firebase.firestore

	private fun usersCollectionRef() = firebaseDb.collection(USERS_COLLECTION)
	private fun allEmailsMobilesRef() =
		firebaseDb.collection(USERS_COLLECTION).document(EMAIL_MOBILE_DOC)


	override suspend fun getUserById(userId: String): Result<UserData?> {
		val resRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		return if (!resRef.isEmpty) {
			Success(resRef.toObjects(UserData::class.java)[0])
		} else {
			Error(Exception("User Not Found!"))
		}
	}


	override suspend fun addUser(userData: UserData) {
		usersCollectionRef().add(userData.toHashMap())
			.addOnSuccessListener {
				Log.d(TAG, "Doc added")
			}
			.addOnFailureListener { e ->
				Log.d(TAG, "firestore error occurred: $e")
			}
	}

	override suspend fun getUserByMobile(phoneNumber: String): UserData =
		usersCollectionRef().whereEqualTo(USERS_MOBILE_FIELD, phoneNumber).get().await()
			.toObjects(UserData::class.java)[0]

	override suspend fun getOrdersByUserId(userId: String): Result<List<UserData.OrderItem>?> {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		return if (!userRef.isEmpty) {
			val userData = userRef.documents[0].toObject(UserData::class.java)
			Success(userData!!.orders)
		} else {
			Error(Exception("User Not Found!"))
		}
	}

	override suspend fun getAddressesByUserId(userId: String): Result<List<UserData.Address>?> {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		return if (!userRef.isEmpty) {
			val userData = userRef.documents[0].toObject(UserData::class.java)
			Success(userData!!.addresses)
		} else {
			Error(Exception("User Not Found!"))
		}
	}

	override suspend fun getLikesByUserId(userId: String): Result<List<String>?> {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		return if (!userRef.isEmpty) {
			val userData = userRef.documents[0].toObject(UserData::class.java)
			Success(userData!!.likes)
		} else {
			Error(Exception("User Not Found!"))
		}
	}

	override suspend fun getUserByMobileAndPassword(
		mobile: String,
		password: String
	): MutableList<UserData> =
		usersCollectionRef().whereEqualTo(USERS_MOBILE_FIELD, mobile)
			.whereEqualTo(USERS_PWD_FIELD, password).get().await().toObjects(UserData::class.java)

	override suspend fun likeProduct(productId: String, userId: String) {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			usersCollectionRef().document(docId)
				.update(USERS_LIKES_FIELD, FieldValue.arrayUnion(productId))
		}
	}

	override suspend fun dislikeProduct(productId: String, userId: String) {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			usersCollectionRef().document(docId)
				.update(USERS_LIKES_FIELD, FieldValue.arrayRemove(productId))
		}
	}

	override suspend fun insertAddress(newAddress: UserData.Address, userId: String) {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			usersCollectionRef().document(docId)
				.update(USERS_ADDRESSES_FIELD, FieldValue.arrayUnion(newAddress.toHashMap()))
		}
	}

	override suspend fun updateAddress(newAddress: UserData.Address, userId: String) {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			val oldAddressList =
				userRef.documents[0].toObject(UserData::class.java)?.addresses?.toMutableList()
			val idx = oldAddressList?.indexOfFirst { it.addressId == newAddress.addressId } ?: -1
			if (idx != -1) {
				oldAddressList?.set(idx, newAddress)
			}
			usersCollectionRef().document(docId)
				.update(USERS_ADDRESSES_FIELD, oldAddressList?.map { it.toHashMap() })
		}
	}

	override suspend fun deleteAddress(addressId: String, userId: String) {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			val oldAddressList =
				userRef.documents[0].toObject(UserData::class.java)?.addresses?.toMutableList()
			val idx = oldAddressList?.indexOfFirst { it.addressId == addressId } ?: -1
			if (idx != -1) {
				oldAddressList?.removeAt(idx)
			}
			usersCollectionRef().document(docId)
				.update(USERS_ADDRESSES_FIELD, oldAddressList?.map { it.toHashMap() })
		}
	}

	override suspend fun insertCartItem(newItem: UserData.CartItem, userId: String) {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			usersCollectionRef().document(docId)
				.update(USERS_CART_FIELD, FieldValue.arrayUnion(newItem.toHashMap()))
		}
	}

	override suspend fun updateCartItem(item: UserData.CartItem, userId: String) {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			val oldCart =
				userRef.documents[0].toObject(UserData::class.java)?.cart?.toMutableList()
			val idx = oldCart?.indexOfFirst { it.itemId == item.itemId } ?: -1
			if (idx != -1) {
				oldCart?.set(idx, item)
			}
			usersCollectionRef().document(docId)
				.update(USERS_CART_FIELD, oldCart?.map { it.toHashMap() })
		}
	}

	override suspend fun deleteCartItem(itemId: String, userId: String) {
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			val oldCart =
				userRef.documents[0].toObject(UserData::class.java)?.cart?.toMutableList()
			val idx = oldCart?.indexOfFirst { it.itemId == itemId } ?: -1
			if (idx != -1) {
				oldCart?.removeAt(idx)
			}
			usersCollectionRef().document(docId)
				.update(USERS_CART_FIELD, oldCart?.map { it.toHashMap() })
		}
	}

	override suspend fun placeOrder(newOrder: UserData.OrderItem, userId: String) {
		// add order to customer and
		// specific items to their owners
		// empty customers cart
		val ownerProducts: MutableMap<String, MutableList<UserData.CartItem>> = mutableMapOf()
		for (item in newOrder.items) {
			if (!ownerProducts.containsKey(item.ownerId)) {
				ownerProducts[item.ownerId] = mutableListOf()
			}
			ownerProducts[item.ownerId]?.add(item)
		}
		ownerProducts.forEach { (ownerId, items) ->
			run {
				val itemPrices = mutableMapOf<String, Double>()
				items.forEach { item ->
					itemPrices[item.itemId] = newOrder.itemsPrices[item.itemId] ?: 0.0
				}
				val ownerOrder = UserData.OrderItem(
					newOrder.orderId,
					userId,
					items,
					itemPrices,
					newOrder.deliveryAddress,
					newOrder.shippingCharges,
					newOrder.paymentMethod,
					newOrder.orderDate,
					OrderStatus.CONFIRMED.name
				)
				val ownerRef =
					usersCollectionRef().whereEqualTo(USERS_ID_FIELD, ownerId).get().await()
				if (!ownerRef.isEmpty) {
					val docId = ownerRef.documents[0].id
					usersCollectionRef().document(docId)
						.update(USERS_ORDERS_FIELD, FieldValue.arrayUnion(ownerOrder.toHashMap()))
				}
			}
		}

		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			usersCollectionRef().document(docId)
				.update(USERS_ORDERS_FIELD, FieldValue.arrayUnion(newOrder.toHashMap()))
			usersCollectionRef().document(docId)
				.update(USERS_CART_FIELD, ArrayList<UserData.CartItem>())
		}
	}

	override suspend fun setStatusOfOrderByUserId(orderId: String, userId: String, status: String) {
		// update on customer and owner
		val userRef = usersCollectionRef().whereEqualTo(USERS_ID_FIELD, userId).get().await()
		if (!userRef.isEmpty) {
			val docId = userRef.documents[0].id
			val ordersList =
				userRef.documents[0].toObject(UserData::class.java)?.orders?.toMutableList()
			val idx = ordersList?.indexOfFirst { it.orderId == orderId } ?: -1
			if (idx != -1) {
				val orderData = ordersList?.get(idx)
				if (orderData != null) {
					usersCollectionRef().document(docId)
						.update(USERS_ORDERS_FIELD, FieldValue.arrayRemove(orderData.toHashMap()))
					orderData.status = status
					usersCollectionRef().document(docId)
						.update(USERS_ORDERS_FIELD, FieldValue.arrayUnion(orderData.toHashMap()))

					// updating customer status
					val custRef =
						usersCollectionRef().whereEqualTo(USERS_ID_FIELD, orderData.customerId)
							.get().await()
					if (!custRef.isEmpty) {
						val did = custRef.documents[0].id
						val orders =
							custRef.documents[0].toObject(UserData::class.java)?.orders?.toMutableList()
						val pos = orders?.indexOfFirst { it.orderId == orderId } ?: -1
						if (pos != -1) {
							val order = orders?.get(pos)
							if (order != null) {
								usersCollectionRef().document(did).update(
									USERS_ORDERS_FIELD,
									FieldValue.arrayRemove(order.toHashMap())
								)
								order.status = status
								usersCollectionRef().document(did).update(
									USERS_ORDERS_FIELD,
									FieldValue.arrayUnion(order.toHashMap())
								)
							}
						}
					}
				}
			}
		}
	}

	override fun updateEmailsAndMobiles(email: String, mobile: String) {
		allEmailsMobilesRef().update(EMAIL_MOBILE_EMAIL_FIELD, FieldValue.arrayUnion(email))
		allEmailsMobilesRef().update(EMAIL_MOBILE_MOB_FIELD, FieldValue.arrayUnion(mobile))
	}

	override suspend fun getEmailsAndMobiles() = allEmailsMobilesRef().get().await().toObject(
		EmailMobileData::class.java
	)

	companion object {
		private const val USERS_COLLECTION = "users"
		private const val USERS_ID_FIELD = "userId"
		private const val USERS_ADDRESSES_FIELD = "addresses"
		private const val USERS_LIKES_FIELD = "likes"
		private const val USERS_CART_FIELD = "cart"
		private const val USERS_ORDERS_FIELD = "orders"
		private const val USERS_MOBILE_FIELD = "mobile"
		private const val USERS_PWD_FIELD = "password"
		private const val EMAIL_MOBILE_DOC = "emailAndMobiles"
		private const val EMAIL_MOBILE_EMAIL_FIELD = "emails"
		private const val EMAIL_MOBILE_MOB_FIELD = "mobiles"
		private const val TAG = "AuthRemoteDataSource"
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/remote/ProductsRemoteDataSource.kt
================================================
package com.vishalgaur.shoppingapp.data.source.remote

import android.net.Uri
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.ktx.storage
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.Result.Error
import com.vishalgaur.shoppingapp.data.Result.Success
import com.vishalgaur.shoppingapp.data.source.ProductDataSource
import kotlinx.coroutines.tasks.await

class ProductsRemoteDataSource : ProductDataSource {
	private val firebaseDb: FirebaseFirestore = Firebase.firestore
	private val firebaseStorage: FirebaseStorage = Firebase.storage

	private val observableProducts = MutableLiveData<Result<List<Product>>?>()

	private fun storageRef() = firebaseStorage.reference
	private fun productsCollectionRef() = firebaseDb.collection(PRODUCT_COLLECTION)

	override suspend fun refreshProducts() {
		observableProducts.value = getAllProducts()
	}

	override fun observeProducts(): LiveData<Result<List<Product>>?> {
		return observableProducts
	}

	override suspend fun getAllProducts(): Result<List<Product>> {
		val resRef = productsCollectionRef().get().await()
		return if (!resRef.isEmpty) {
			Success(resRef.toObjects(Product::class.java))
		} else {
			Error(Exception("Error getting Products!"))
		}
	}

	override suspend fun insertProduct(newProduct: Product) {
		productsCollectionRef().add(newProduct.toHashMap()).await()
	}

	override suspend fun updateProduct(proData: Product) {
		val resRef =
			productsCollectionRef().whereEqualTo(PRODUCT_ID_FIELD, proData.productId).get().await()
		if (!resRef.isEmpty) {
			val docId = resRef.documents[0].id
			productsCollectionRef().document(docId).set(proData.toHashMap()).await()
		} else {
			Log.d(TAG, "onUpdateProduct: product with id: $proData.productId not found!")
		}
	}

	override suspend fun getProductById(productId: String): Result<Product> {
		val resRef = productsCollectionRef().whereEqualTo(PRODUCT_ID_FIELD, productId).get().await()
		return if (!resRef.isEmpty) {
			Success(resRef.toObjects(Product::class.java)[0])
		} else {
			Error(Exception("Product with id: $productId Not Found!"))
		}
	}

	override suspend fun deleteProduct(productId: String) {
		Log.d(TAG, "onDeleteProduct: delete product with Id: $productId initiated")
		val resRef = productsCollectionRef().whereEqualTo(PRODUCT_ID_FIELD, productId).get().await()
		if (!resRef.isEmpty) {
			val product = resRef.documents[0].toObject(Product::class.java)
			val imgUrls = product?.images

			//deleting images first
			imgUrls?.forEach { imgUrl ->
				deleteImage(imgUrl)
			}

			//deleting doc containing product
			val docId = resRef.documents[0].id
			productsCollectionRef().document(docId).delete().addOnSuccessListener {
				Log.d(TAG, "onDelete: DocumentSnapshot successfully deleted!")
			}.addOnFailureListener { e ->
				Log.w(TAG, "onDelete: Error deleting document", e)
			}
		} else {
			Log.d(TAG, "onDeleteProduct: product with id: $productId not found!")
		}
	}

	override suspend fun uploadImage(uri: Uri, fileName: String): Uri? {
		val imgRef = storageRef().child("$SHOES_STORAGE_PATH/$fileName")
		val uploadTask = imgRef.putFile(uri)
		val uriRef = uploadTask.continueWithTask { task ->
			if (!task.isSuccessful) {
				task.exception?.let { throw it }
			}
			imgRef.downloadUrl
		}
		return uriRef.await()
	}

	override fun deleteImage(imgUrl: String) {
		val ref = firebaseStorage.getReferenceFromUrl(imgUrl)
		ref.delete().addOnSuccessListener {
			Log.d(TAG, "onDelete: image deleted successfully!")
		}.addOnFailureListener { e ->
			Log.d(TAG, "onDelete: Error deleting image, error: $e")
		}
	}

	override fun revertUpload(fileName: String) {
		val imgRef = storageRef().child("${SHOES_STORAGE_PATH}/$fileName")
		imgRef.delete().addOnSuccessListener {
			Log.d(TAG, "onRevert: File with name: $fileName deleted successfully!")
		}.addOnFailureListener { e ->
			Log.d(TAG, "onRevert: Error deleting file with name = $fileName, error: $e")
		}
	}

	companion object {
		private const val PRODUCT_COLLECTION = "products"
		private const val PRODUCT_ID_FIELD = "productId"
		private const val SHOES_STORAGE_PATH = "Shoes"
		private const val TAG = "ProductsRemoteSource"
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/repository/AuthRepoInterface.kt
================================================
package com.vishalgaur.shoppingapp.data.source.repository

import android.content.Context
import androidx.lifecycle.MutableLiveData
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.PhoneAuthCredential
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.utils.SignUpErrors

interface AuthRepoInterface {
	suspend fun refreshData()
	suspend fun signUp(userData: UserData)
	fun login(userData: UserData, rememberMe: Boolean)
	suspend fun checkEmailAndMobile(email: String, mobile: String, context: Context): SignUpErrors?
	suspend fun checkLogin(mobile: String, password: String): UserData?
	suspend fun signOut()
	suspend fun hardRefreshUserData()
	suspend fun insertProductToLikes(productId: String, userId: String): Result<Boolean>
	suspend fun removeProductFromLikes(productId: String, userId: String): Result<Boolean>
	suspend fun insertAddress(newAddress: UserData.Address, userId: String): Result<Boolean>
	suspend fun updateAddress(newAddress: UserData.Address, userId: String): Result<Boolean>
	suspend fun deleteAddressById(addressId: String, userId: String): Result<Boolean>
	suspend fun insertCartItemByUserId(cartItem: UserData.CartItem, userId: String): Result<Boolean>
	suspend fun updateCartItemByUserId(cartItem: UserData.CartItem, userId: String): Result<Boolean>
	suspend fun deleteCartItemByUserId(itemId: String, userId: String): Result<Boolean>
	suspend fun placeOrder(newOrder: UserData.OrderItem, userId: String): Result<Boolean>
	suspend fun setStatusOfOrder(orderId: String, userId: String, status: String): Result<Boolean>
	suspend fun getOrdersByUserId(userId: String): Result<List<UserData.OrderItem>?>
	suspend fun getAddressesByUserId(userId: String): Result<List<UserData.Address>?>
	suspend fun getLikesByUserId(userId: String): Result<List<String>?>
	suspend fun getUserData(userId: String): Result<UserData?>
	fun getFirebaseAuth(): FirebaseAuth
	fun signInWithPhoneAuthCredential(
		credential: PhoneAuthCredential,
		isUserLoggedIn: MutableLiveData<Boolean>,
		context: Context
	)

	fun isRememberMeOn(): Boolean
}


================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/repository/AuthRepository.kt
================================================
package com.vishalgaur.shoppingapp.data.source.repository

import android.content.Context
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.MutableLiveData
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException
import com.google.firebase.auth.PhoneAuthCredential
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.Result.Error
import com.vishalgaur.shoppingapp.data.Result.Success
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.source.UserDataSource
import com.vishalgaur.shoppingapp.data.utils.SignUpErrors
import com.vishalgaur.shoppingapp.data.utils.UserType
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope

class AuthRepository(
	private val userLocalDataSource: UserDataSource,
	private val authRemoteDataSource: UserDataSource,
	private var sessionManager: ShoppingAppSessionManager
) : AuthRepoInterface {

	private var firebaseAuth: FirebaseAuth = Firebase.auth

	companion object {
		private const val TAG = "AuthRepository"
	}

	override fun getFirebaseAuth() = firebaseAuth

	override fun isRememberMeOn() = sessionManager.isRememberMeOn()

	override suspend fun refreshData() {
		Log.d(TAG, "refreshing userdata")
		if (sessionManager.isLoggedIn()) {
			updateUserInLocalSource(sessionManager.getPhoneNumber())
		} else {
			sessionManager.logoutFromSession()
			deleteUserFromLocalSource()
		}
	}

	override suspend fun signUp(userData: UserData) {
		val isSeller = userData.userType == UserType.SELLER.name
		sessionManager.createLoginSession(
			userData.userId,
			userData.name,
			userData.mobile,
			false,
			isSeller
		)
		Log.d(TAG, "on SignUp: Updating user in Local Source")
		userLocalDataSource.addUser(userData)
		Log.d(TAG, "on SignUp: Updating userdata on Remote Source")
		authRemoteDataSource.addUser(userData)
		authRemoteDataSource.updateEmailsAndMobiles(userData.email, userData.mobile)
	}

	override fun login(userData: UserData, rememberMe: Boolean) {
		val isSeller = userData.userType == UserType.SELLER.name
		sessionManager.createLoginSession(
			userData.userId,
			userData.name,
			userData.mobile,
			rememberMe,
			isSeller
		)
	}

	override suspend fun checkEmailAndMobile(
		email: String,
		mobile: String,
		context: Context
	): SignUpErrors? {
		Log.d(TAG, "on SignUp: Checking email and mobile")
		var sErr: SignUpErrors? = null
		try {
			val queryResult = authRemoteDataSource.getEmailsAndMobiles()
			if (queryResult != null) {
				val mob = queryResult.mobiles.contains(mobile)
				val em = queryResult.emails.contains(email)
				if (!mob && !em) {
					sErr = SignUpErrors.NONE
				} else {
					sErr = SignUpErrors.SERR
					when {
						!mob && em -> makeErrToast("Email is already registered!", context)
						mob && !em -> makeErrToast("Mobile is already registered!", context)
						mob && em -> makeErrToast(
							"Email and mobile is already registered!",
							context
						)
					}
				}
			}
		} catch (e: Exception) {
			makeErrToast("Some Error Occurred", context)
		}
		return sErr
	}

	override suspend fun checkLogin(mobile: String, password: String): UserData? {
		Log.d(TAG, "on Login: checking mobile and password")
		var queryResult = mutableListOf<UserData>()
		try {
			queryResult = authRemoteDataSource.getUserByMobileAndPassword(mobile, password)
		} catch (e: Exception) {
			// No Handling
		}
		return if (queryResult.size > 0) {
			queryResult[0]
		} else {
			null
		}
	}

	override fun signInWithPhoneAuthCredential(
		credential: PhoneAuthCredential,
		isUserLoggedIn: MutableLiveData<Boolean>, context: Context
	) {
		try {
			firebaseAuth.signInWithCredential(credential)
				.addOnCompleteListener { task ->
					if (task.isSuccessful) {
						Log.d(TAG, "signInWithCredential:success")
						val user = task.result?.user
						if (user != null) {
							isUserLoggedIn.postValue(true)
						}

					} else {
						Log.w(TAG, "signInWithCredential:failure", task.exception)
						if (task.exception is FirebaseAuthInvalidCredentialsException) {
							Log.d(TAG, "createUserWithMobile:failure", task.exception)
							isUserLoggedIn.postValue(false)
							makeErrToast("Wrong OTP!", context)
						}
					}
				}.addOnFailureListener {
					Log.d(TAG, "createUserWithMobile:failure", it)
					isUserLoggedIn.postValue(false)
					makeErrToast("Invalid Request!", context)
				}
		} catch (e: Exception) {
			makeErrToast("Some Error Occurred", context)
		}
	}

	override suspend fun signOut() {
		sessionManager.logoutFromSession()
		firebaseAuth.signOut()
		userLocalDataSource.clearUser()
	}

	private fun makeErrToast(text: String, context: Context) {
		Toast.makeText(context, text, Toast.LENGTH_LONG).show()
	}

	private suspend fun deleteUserFromLocalSource() {
		userLocalDataSource.clearUser()
	}

	private suspend fun updateUserInLocalSource(phoneNumber: String?) {
		coroutineScope {
			launch {
				if (phoneNumber != null) {
					val getUser = userLocalDataSource.getUserByMobile(phoneNumber)
					if (getUser == null) {
						userLocalDataSource.clearUser()
						val uData = authRemoteDataSource.getUserByMobile(phoneNumber)
						if (uData != null) {
							userLocalDataSource.addUser(uData)
						}
					}
				}
			}
		}
	}

	override suspend fun hardRefreshUserData() {
		userLocalDataSource.clearUser()
		val mobile = sessionManager.getPhoneNumber()
		if (mobile != null) {
			val uData = authRemoteDataSource.getUserByMobile(mobile)
			if (uData != null) {
				userLocalDataSource.addUser(uData)
			}
		}
	}

	override suspend fun insertProductToLikes(productId: String, userId: String): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onLikeProduct: adding product to remote source")
				authRemoteDataSource.likeProduct(productId, userId)
			}
			val localRes = async {
				Log.d(TAG, "onLikeProduct: updating product to local source")
				userLocalDataSource.likeProduct(productId, userId)
			}
			try {
				localRes.await()
				remoteRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun removeProductFromLikes(
		productId: String,
		userId: String
	): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onDislikeProduct: deleting product from remote source")
				authRemoteDataSource.dislikeProduct(productId, userId)
			}
			val localRes = async {
				Log.d(TAG, "onDislikeProduct: updating product to local source")
				userLocalDataSource.dislikeProduct(productId, userId)
			}
			try {
				localRes.await()
				remoteRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun insertAddress(
		newAddress: UserData.Address,
		userId: String
	): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onInsertAddress: adding address to remote source")
				authRemoteDataSource.insertAddress(newAddress, userId)
			}
			val localRes = async {
				Log.d(TAG, "onInsertAddress: updating address to local source")
				val userRes = authRemoteDataSource.getUserById(userId)
				if (userRes is Success) {
					userLocalDataSource.clearUser()
					userLocalDataSource.addUser(userRes.data!!)
				} else if (userRes is Error) {
					throw userRes.exception
				}
			}
			try {
				remoteRes.await()
				localRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun updateAddress(
		newAddress: UserData.Address,
		userId: String
	): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onUpdateAddress: updating address on remote source")
				authRemoteDataSource.updateAddress(newAddress, userId)
			}
			val localRes = async {
				Log.d(TAG, "onUpdateAddress: updating address on local source")
				val userRes =
					authRemoteDataSource.getUserById(userId)
				if (userRes is Success) {
					userLocalDataSource.clearUser()
					userLocalDataSource.addUser(userRes.data!!)
				} else if (userRes is Error) {
					throw userRes.exception
				}
			}
			try {
				remoteRes.await()
				localRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun deleteAddressById(addressId: String, userId: String): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onDelete: deleting address from remote source")
				authRemoteDataSource.deleteAddress(addressId, userId)
			}
			val localRes = async {
				Log.d(TAG, "onDelete: deleting address from local source")
				val userRes =
					authRemoteDataSource.getUserById(userId)
				if (userRes is Success) {
					userLocalDataSource.clearUser()
					userLocalDataSource.addUser(userRes.data!!)
				} else if (userRes is Error) {
					throw userRes.exception
				}
			}
			try {
				remoteRes.await()
				localRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun insertCartItemByUserId(
		cartItem: UserData.CartItem,
		userId: String
	): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onInsertCartItem: adding item to remote source")
				authRemoteDataSource.insertCartItem(cartItem, userId)
			}
			val localRes = async {
				Log.d(TAG, "onInsertCartItem: updating item to local source")
				userLocalDataSource.insertCartItem(cartItem, userId)
			}
			try {
				localRes.await()
				remoteRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun updateCartItemByUserId(
		cartItem: UserData.CartItem,
		userId: String
	): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onUpdateCartItem: updating cart item on remote source")
				authRemoteDataSource.updateCartItem(cartItem, userId)
			}
			val localRes = async {
				Log.d(TAG, "onUpdateCartItem: updating cart item on local source")
				userLocalDataSource.updateCartItem(cartItem, userId)
			}
			try {
				localRes.await()
				remoteRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun deleteCartItemByUserId(itemId: String, userId: String): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onDelete: deleting cart item from remote source")
				authRemoteDataSource.deleteCartItem(itemId, userId)
			}
			val localRes = async {
				Log.d(TAG, "onDelete: deleting cart item from local source")
				userLocalDataSource.deleteCartItem(itemId, userId)
			}
			try {
				localRes.await()
				remoteRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun placeOrder(newOrder: UserData.OrderItem, userId: String): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onPlaceOrder: adding item to remote source")
				authRemoteDataSource.placeOrder(newOrder, userId)
			}
			val localRes = async {
				Log.d(TAG, "onPlaceOrder: adding item to local source")
				val userRes = authRemoteDataSource.getUserById(userId)
				if (userRes is Success) {
					userLocalDataSource.clearUser()
					userLocalDataSource.addUser(userRes.data!!)
				} else if (userRes is Error) {
					throw userRes.exception
				}
			}
			try {
				remoteRes.await()
				localRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun setStatusOfOrder(
		orderId: String,
		userId: String,
		status: String
	): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onSetStatus: updating status on remote source")
				authRemoteDataSource.setStatusOfOrderByUserId(orderId, userId, status)
			}
			val localRes = async {
				Log.d(TAG, "onSetStatus: updating status on local source")
				userLocalDataSource.setStatusOfOrderByUserId(orderId, userId, status)
			}
			try {
				localRes.await()
				remoteRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun getOrdersByUserId(userId: String): Result<List<UserData.OrderItem>?> {
		return userLocalDataSource.getOrdersByUserId(userId)
	}

	override suspend fun getAddressesByUserId(userId: String): Result<List<UserData.Address>?> {
		return userLocalDataSource.getAddressesByUserId(userId)
	}

	override suspend fun getLikesByUserId(userId: String): Result<List<String>?> {
		return userLocalDataSource.getLikesByUserId(userId)
	}

	override suspend fun getUserData(userId: String): Result<UserData?> {
		return userLocalDataSource.getUserById(userId)
	}

}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/repository/ProductsRepoInterface.kt
================================================
package com.vishalgaur.shoppingapp.data.source.repository

import android.net.Uri
import androidx.lifecycle.LiveData
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.utils.StoreDataStatus

interface ProductsRepoInterface {
	suspend fun refreshProducts(): StoreDataStatus?
	fun observeProducts(): LiveData<Result<List<Product>>?>
	fun observeProductsByOwner(ownerId: String): LiveData<Result<List<Product>>?>
	suspend fun getAllProductsByOwner(ownerId: String): Result<List<Product>>
	suspend fun getProductById(productId: String, forceUpdate: Boolean = false): Result<Product>
	suspend fun insertProduct(newProduct: Product): Result<Boolean>
	suspend fun insertImages(imgList: List<Uri>): List<String>
	suspend fun updateProduct(product: Product): Result<Boolean>
	suspend fun updateImages(newList: List<Uri>, oldList: List<String>): List<String>
	suspend fun deleteProductById(productId: String): Result<Boolean>
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/source/repository/ProductsRepository.kt
================================================
package com.vishalgaur.shoppingapp.data.source.repository

import android.net.Uri
import android.util.Log
import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import com.vishalgaur.shoppingapp.ERR_UPLOAD
import com.vishalgaur.shoppingapp.data.Product
import com.vishalgaur.shoppingapp.data.Result
import com.vishalgaur.shoppingapp.data.Result.*
import com.vishalgaur.shoppingapp.data.source.ProductDataSource
import com.vishalgaur.shoppingapp.data.utils.StoreDataStatus
import kotlinx.coroutines.async
import kotlinx.coroutines.supervisorScope
import java.util.*

class ProductsRepository(
	private val productsRemoteSource: ProductDataSource,
	private val productsLocalSource: ProductDataSource
) : ProductsRepoInterface {

	companion object {
		private const val TAG = "ProductsRepository"
	}

	override suspend fun refreshProducts(): StoreDataStatus? {
		Log.d(TAG, "Updating Products in Room")
		return updateProductsFromRemoteSource()
	}

	override fun observeProducts(): LiveData<Result<List<Product>>?> {
		return productsLocalSource.observeProducts()
	}

	override fun observeProductsByOwner(ownerId: String): LiveData<Result<List<Product>>?> {
		return productsLocalSource.observeProductsByOwner(ownerId)
	}

	override suspend fun getAllProductsByOwner(ownerId: String): Result<List<Product>> {
		return productsLocalSource.getAllProductsByOwner(ownerId)
	}

	override suspend fun getProductById(productId: String, forceUpdate: Boolean): Result<Product> {
		if (forceUpdate) {
			updateProductFromRemoteSource(productId)
		}
		return productsLocalSource.getProductById(productId)
	}

	override suspend fun insertProduct(newProduct: Product): Result<Boolean> {
		return supervisorScope {
			val localRes = async {
				Log.d(TAG, "onInsertProduct: adding product to local source")
				productsLocalSource.insertProduct(newProduct)
			}
			val remoteRes = async {
				Log.d(TAG, "onInsertProduct: adding product to remote source")
				productsRemoteSource.insertProduct(newProduct)
			}
			try {
				localRes.await()
				remoteRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun insertImages(imgList: List<Uri>): List<String> {
		var urlList = mutableListOf<String>()
		imgList.forEach label@{ uri ->
			val uniId = UUID.randomUUID().toString()
			val fileName = uniId + uri.lastPathSegment?.split("/")?.last()
			try {
				val downloadUrl = productsRemoteSource.uploadImage(uri, fileName)
				urlList.add(downloadUrl.toString())
			} catch (e: Exception) {
				productsRemoteSource.revertUpload(fileName)
				Log.d(TAG, "exception: message = $e")
				urlList = mutableListOf()
				urlList.add(ERR_UPLOAD)
				return@label
			}
		}
		return urlList
	}

	override suspend fun updateProduct(product: Product): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onUpdate: updating product in remote source")
				productsRemoteSource.updateProduct(product)
			}
			val localRes = async {
				Log.d(TAG, "onUpdate: updating product in local source")
				productsLocalSource.insertProduct(product)
			}
			try {
				remoteRes.await()
				localRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	override suspend fun updateImages(newList: List<Uri>, oldList: List<String>): List<String> {
		var urlList = mutableListOf<String>()
		newList.forEach label@{ uri ->
			if (!oldList.contains(uri.toString())) {
				val uniId = UUID.randomUUID().toString()
				val fileName = uniId + uri.lastPathSegment?.split("/")?.last()
				try {
					val downloadUrl = productsRemoteSource.uploadImage(uri, fileName)
					urlList.add(downloadUrl.toString())
				} catch (e: Exception) {
					productsRemoteSource.revertUpload(fileName)
					Log.d(TAG, "exception: message = $e")
					urlList = mutableListOf()
					urlList.add(ERR_UPLOAD)
					return@label
				}
			} else {
				urlList.add(uri.toString())
			}
		}
		oldList.forEach { imgUrl ->
			if (!newList.contains(imgUrl.toUri())) {
				productsRemoteSource.deleteImage(imgUrl)
			}
		}
		return urlList
	}

	override suspend fun deleteProductById(productId: String): Result<Boolean> {
		return supervisorScope {
			val remoteRes = async {
				Log.d(TAG, "onDelete: deleting product from remote source")
				productsRemoteSource.deleteProduct(productId)
			}
			val localRes = async {
				Log.d(TAG, "onDelete: deleting product from local source")
				productsLocalSource.deleteProduct(productId)
			}
			try {
				remoteRes.await()
				localRes.await()
				Success(true)
			} catch (e: Exception) {
				Error(e)
			}
		}
	}

	private suspend fun updateProductsFromRemoteSource(): StoreDataStatus? {
		var res: StoreDataStatus? = null
		try {
			val remoteProducts = productsRemoteSource.getAllProducts()
			if (remoteProducts is Success) {
				Log.d(TAG, "pro list = ${remoteProducts.data}")
				productsLocalSource.deleteAllProducts()
				productsLocalSource.insertMultipleProducts(remoteProducts.data)
				res = StoreDataStatus.DONE
			} else {
				res = StoreDataStatus.ERROR
				if (remoteProducts is Error)
					throw remoteProducts.exception
			}
		} catch (e: Exception) {
			Log.d(TAG, "onUpdateProductsFromRemoteSource: Exception occurred, ${e.message}")
		}

		return res
	}

	private suspend fun updateProductFromRemoteSource(productId: String): StoreDataStatus? {
		var res: StoreDataStatus? = null
		try {
			val remoteProduct = productsRemoteSource.getProductById(productId)
			if (remoteProduct is Success) {
				productsLocalSource.insertProduct(remoteProduct.data)
				res = StoreDataStatus.DONE
			} else {
				res = StoreDataStatus.ERROR
				if (remoteProduct is Error)
					throw remoteProduct.exception
			}
		} catch (e: Exception) {
			Log.d(TAG, "onUpdateProductFromRemoteSource: Exception occurred, ${e.message}")
		}
		return res
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/utils/DateTypeConvertors.kt
================================================
package com.vishalgaur.shoppingapp.data.utils

import androidx.room.TypeConverter
import java.util.*

class DateTypeConvertors {
	@TypeConverter
	fun toDate(dateLong: Long?): Date? {
		return dateLong?.let { Date(it) }
	}

	@TypeConverter
	fun fromDate(date: Date?): Long? {
		return date?.time
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/utils/EmailMobileData.kt
================================================
package com.vishalgaur.shoppingapp.data.utils

data class EmailMobileData(
	val emails: ArrayList<String> = ArrayList(),
	val mobiles: ArrayList<String> = ArrayList()
)

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/utils/ListTypeConverter.kt
================================================
package com.vishalgaur.shoppingapp.data.utils

import androidx.room.TypeConverter

class ListTypeConverter {
	@TypeConverter
	fun fromStringToStringList(value: String): List<String> {
		return value.split(",").map { it }
	}

	@TypeConverter
	fun fromStringListToString(value: List<String>): String {
		return value.joinToString(separator = ",")
	}

	@TypeConverter
	fun fromStringToIntegerList(value: String): List<Int> {
		return value.split(",").map { it.toInt() }
	}

	@TypeConverter
	fun fromIntegerListToString(value: List<Int>): String {
		return value.joinToString(separator = ",")
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/utils/ObjectListTypeConvertor.kt
================================================
package com.vishalgaur.shoppingapp.data.utils

import androidx.room.TypeConverter
import com.google.common.reflect.TypeToken
import com.google.gson.Gson
import com.vishalgaur.shoppingapp.data.UserData

class ObjectListTypeConvertor {
	@TypeConverter
	fun stringToAddressObjectList(data: String?): List<UserData.Address> {
		if (data.isNullOrBlank()) {
			return emptyList()
		}
		val listType = object : TypeToken<List<UserData.Address>>() {}.type
		val gson = Gson()
		return gson.fromJson(data, listType)
	}

	@TypeConverter
	fun addressObjectListToString(addressList: List<UserData.Address>): String {
		if (addressList.isEmpty()) {
			return ""
		}
		val gson = Gson()
		val listType = object : TypeToken<List<UserData.Address>>() {}.type
		return gson.toJson(addressList, listType)
	}

	@TypeConverter
	fun stringToCartObjectList(data: String?): List<UserData.CartItem> {
		if (data.isNullOrBlank()) {
			return emptyList()
		}
		val listType = object : TypeToken<List<UserData.CartItem>>() {}.type
		val gson = Gson()
		return gson.fromJson(data, listType)
	}

	@TypeConverter
	fun cartObjectListToString(cartList: List<UserData.CartItem>): String {
		if (cartList.isEmpty()) {
			return ""
		}
		val gson = Gson()
		val listType = object : TypeToken<List<UserData.CartItem>>() {}.type
		return gson.toJson(cartList, listType)
	}

	@TypeConverter
	fun stringToOrderObjectList(data: String?): List<UserData.OrderItem> {
		if (data.isNullOrBlank()) {
			return emptyList()
		}
		val listType = object : TypeToken<List<UserData.OrderItem>>() {}.type
		val gson = Gson()
		return gson.fromJson(data, listType)
	}

	@TypeConverter
	fun orderObjectListToString(orderList: List<UserData.OrderItem>): String {
		if (orderList.isEmpty()) {
			return ""
		}
		val gson = Gson()
		val listType = object : TypeToken<List<UserData.OrderItem>>() {}.type
		return gson.toJson(orderList, listType)
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/utils/ProductUtils.kt
================================================
package com.vishalgaur.shoppingapp.data.utils

val ShoeSizes = mapOf(
	"UK4" to 4,
	"UK5" to 5,
	"UK6" to 6,
	"UK7" to 7,
	"UK8" to 8,
	"UK9" to 9,
	"UK10" to 10,
	"UK11" to 11,
	"UK12" to 12
)

val ShoeColors = mapOf(
	"black" to "#000000",
	"white" to "#FFFFFF",
	"red" to "#FF0000",
	"green" to "#00FF00",
	"blue" to "#0000FF",
	"yellow" to "#FFFF00",
	"cyan" to "#00FFFF",
	"magenta" to "#FF00FF"
)

val ProductCategories = arrayOf("Shoes", "Slippers")

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/data/utils/Utils.kt
================================================
package com.vishalgaur.shoppingapp.data.utils

import java.util.*

enum class SignUpErrors { NONE, SERR }

enum class LogInErrors { NONE, LERR }

enum class AddProductErrors { NONE, ERR_ADD, ERR_ADD_IMG, ADDING }

enum class AddObjectStatus { DONE, ERR_ADD, ADDING }

enum class UserType { CUSTOMER, SELLER }

enum class OrderStatus { CONFIRMED, PACKAGING, PACKED, SHIPPING, SHIPPED, ARRIVING, DELIVERED }

enum class StoreDataStatus { LOADING, ERROR, DONE }

fun getISOCountriesMap(): Map<String, String> {
	val result = mutableMapOf<String, String>()
	val isoCountries = Locale.getISOCountries()
	val countriesList = isoCountries.map { isoCountry ->
		result[isoCountry] = Locale("", isoCountry).displayCountry
	}
	return result
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/ui/LaunchActivity.kt
================================================
package com.vishalgaur.shoppingapp.ui

import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.appcompat.app.AppCompatActivity
import com.vishalgaur.shoppingapp.R
import com.vishalgaur.shoppingapp.data.ShoppingAppSessionManager
import com.vishalgaur.shoppingapp.ui.loginSignup.LoginSignupActivity

class LaunchActivity : AppCompatActivity() {

	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_launch)
		setLaunchScreenTimeOut()
	}

	private fun setLaunchScreenTimeOut() {
		Looper.myLooper()?.let {
			Handler(it).postDelayed({
				startPreferredActivity()
			}, TIME_OUT)
		}
	}

	private fun startPreferredActivity() {
		val sessionManager = ShoppingAppSessionManager(this)
		if (sessionManager.isLoggedIn()) {
			launchHome(this)
			finish()
		} else {
			val intent = Intent(this, LoginSignupActivity::class.java)
			startActivity(intent)
			finish()
		}
	}

	companion object {
		private const val TIME_OUT: Long = 1500
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/ui/RecyclerViewPaddingItemDecoration.kt
================================================
package com.vishalgaur.shoppingapp.ui

import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView

class RecyclerViewPaddingItemDecoration(private val context: Context) :
	RecyclerView.ItemDecoration() {
	private val paddingSpace = 16

	override fun getItemOffsets(
		outRect: Rect,
		view: View,
		parent: RecyclerView,
		state: RecyclerView.State
	) {
		super.getItemOffsets(outRect, view, parent, state)
		outRect.set(paddingSpace, paddingSpace, paddingSpace, paddingSpace)
	}
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/ui/UiUtils.kt
================================================
package com.vishalgaur.shoppingapp.ui

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.vishalgaur.shoppingapp.data.UserData
import com.vishalgaur.shoppingapp.data.utils.getISOCountriesMap
import com.vishalgaur.shoppingapp.ui.home.MainActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.max

enum class SignUpViewErrors { NONE, ERR_EMAIL, ERR_MOBILE, ERR_EMAIL_MOBILE, ERR_EMPTY, ERR_NOT_ACC, ERR_PWD12NS }

enum class LoginViewErrors { NONE, ERR_EMPTY, ERR_MOBILE }

enum class OTPStatus { NONE, CORRECT, WRONG, INVALID_REQ }

enum class AddProductViewErrors { NONE, EMPTY, ERR_PRICE_0 }

enum class AddAddressViewErrors { EMPTY, ERR_FNAME_EMPTY, ERR_LNAME_EMPTY, ERR_STR1_EMPTY, ERR_CITY_EMPTY, ERR_STATE_EMPTY, ERR_ZIP_EMPTY, ERR_ZIP_INVALID, ERR_PHONE_INVALID, ERR_PHONE_EMPTY }

enum class AddItemErrors { ERROR_SIZE, ERROR_COLOR }

class MyOnFocusChangeListener : View.OnFocusChangeListener {
	override fun onFocusChange(v: View?, hasFocus: Boolean) {
		if (v != null) {
			val inputManager =
				v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
			if (!hasFocus) {

				inputManager.hideSoftInputFromWindow(v.windowToken, 0)
			} else {
				inputManager.toggleSoftInputFromWindow(v.windowToken, 0, 0)

			}
		}
	}
}

fun <T> throttleLatest(
	intervalMs: Long = 300L,
	coroutineScope: CoroutineScope,
	destinationFunction: (T) -> Unit
): (T) -> Unit {
	var throttleJob: Job? = null
	var latestParam: T
	return { param: T ->
		latestParam = param
		if (throttleJob?.isCompleted != false) {
			throttleJob = coroutineScope.launch {
				delay(intervalMs)
				latestParam.let(destinationFunction)
			}
		}
	}
}

fun <T> debounce(
	waitMs: Long = 300L,
	coroutineScope: CoroutineScope,
	destinationFunction: (T) -> Unit
): (T) -> Unit {
	var debounceJob: Job? = null
	return { param: T ->
		debounceJob?.cancel()
		debounceJob = coroutineScope.launch {
			delay(waitMs)
			destinationFunction(param)
		}
	}
}

class DotsIndicatorDecoration(
	private val radius: Float,
	private val indicatorItemPadding: Float,
	private val indicatorHeight: Int,
	@ColorInt private val colorInactive: Int,
	@ColorInt private val colorActive: Int
) : RecyclerView.ItemDecoration() {

	private val inactivePaint = Paint()
	private val activePaint = Paint()

	init {
		val width = Resources.getSystem().displayMetrics.density * 1
		inactivePaint.apply {
			strokeCap = Paint.Cap.ROUND
			strokeWidth = width
			style = Paint.Style.STROKE
			isAntiAlias = true
			color = colorInactive
		}

		activePaint.apply {
			strokeCap = Paint.Cap.ROUND
			strokeWidth = width
			style = Paint.Style.FILL_AND_STROKE
			isAntiAlias = true
			color = colorActive
		}
	}

	override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
		super.onDrawOver(c, parent, state)

		val adapter = parent.adapter ?: return

		val itemCount = adapter.itemCount

		val totalLength: Float = (radius * 2 * itemCount)
		val padBWItems = max(0, itemCount - 1) * indicatorItemPadding
		val indicatorTotalWidth = totalLength + padBWItems
		val indicatorStartX = (parent.width - indicatorTotalWidth) / 2F

		val indicatorPosY = parent.height - indicatorHeight / 2F

		drawInactiveDots(c, indicatorStartX, indicatorPosY, itemCount)

		val activePos: Int =
			(parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
		if (activePos == RecyclerView.NO_POSITION) {
			return
		}

		val activeChild =
			(parent.layoutManager as LinearLayoutManager).findViewByPosition(activePos)
				?: return

		drawActiveDot(c, indicatorStartX, indicatorPosY, activePos)


	}

	private fun drawInactiveDots(
		c: Canvas,
		indicatorStartX: Float,
		indicatorPosY: Float,
		itemCount: Int
	) {
		val w = radius * 2 + indicatorItemPadding
		var st = indicatorStartX + radius
		for (i in 1..itemCount) {
			c.drawCircle(st, indicatorPosY, radius, inactivePaint)
			st += w
		}
	}

	private fun drawActiveDot(
		c: Canvas,
		indicatorStartX: Float,
		indicatorPosY: Float,
		highlightPos: Int
	) {
		val w = radius * 2 + indicatorItemPadding
		val highStart = indicatorStartX + radius + w * highlightPos
		c.drawCircle(highStart, indicatorPosY, radius, activePaint)
	}

	override fun getItemOffsets(
		outRect: Rect,
		view: View,
		parent: RecyclerView,
		state: RecyclerView.State
	) {
		super.getItemOffsets(outRect, view, parent, state)
		outRect.bottom = indicatorHeight
	}

}

internal fun launchHome(context: Context) {
	val homeIntent = Intent(context, MainActivity::class.java)
	homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
		.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
		.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
	context.startActivity(homeIntent)
}

internal fun getCompleteAddress(address: UserData.Address): String {
	return if (address.streetAddress2.isBlank()) {
		"${address.streetAddress}, ${address.city}, ${address.state} - ${address.zipCode}, ${getISOCountriesMap()[address.countryISOCode]}"
	} else {
		"${address.streetAddress}, ${address.streetAddress2}, ${address.city}, ${address.state} - ${address.zipCode}, ${getISOCountriesMap()[address.countryISOCode]}"
	}
}

internal fun disableClickOnWindow(activity: Activity) {
	activity.window.setFlags(
		WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
		WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
	)
}

internal fun enableClickOnWindow(activity: Activity) {
	activity.window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
}

================================================
FILE: app/src/main/java/com/vishalgaur/shoppingapp/ui/home/AccountFragment.kt
================================================
package com.vishalgaur.shoppingapp.ui.home

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.vishalgaur.shoppingapp.R
import com.vishalgaur.shoppingapp.databinding.FragmentAccountBinding
import com.vishalgaur.shoppingapp.ui.loginSignup.LoginSignupActivity
import com.vishalgaur.shoppingapp.viewModels.HomeViewModel

private const val TAG = "AccountFragment"

class AccountFragment : Fragment() {

	private lateinit var binding: FragmentAccountBind
Download .txt
gitextract_6suav1uc/

├── .gitignore
├── .idea/
│   ├── .gitignore
│   ├── .name
│   ├── codeStyles/
│   │   ├── Project.xml
│   │   └── codeStyleConfig.xml
│   ├── compiler.xml
│   ├── gradle.xml
│   ├── jarRepositories.xml
│   ├── misc.xml
│   ├── render.experimental.xml
│   ├── runConfigurations.xml
│   └── vcs.xml
├── LICENSE
├── README.md
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── vishalgaur/
│       │               └── shoppingapp/
│       │                   ├── AppDatabaseTest.kt
│       │                   ├── ClickClickableSpan.kt
│       │                   ├── ExampleInstrumentedTest.kt
│       │                   ├── LiveDataTestUtil.kt
│       │                   ├── MainCoroutineRule.kt
│       │                   ├── RecyclerViewMatcherUtils.kt
│       │                   ├── data/
│       │                   │   └── source/
│       │                   │       ├── FakeAuthRepository.kt
│       │                   │       ├── FakeProductsDataSource.kt
│       │                   │       ├── FakeProductsRepository.kt
│       │                   │       ├── FakeUserDataSource.kt
│       │                   │       └── repository/
│       │                   │           ├── AuthRepositoryTest.kt
│       │                   │           └── ProductsRepositoryTest.kt
│       │                   ├── ui/
│       │                   │   ├── home/
│       │                   │   │   ├── HomeFragmentTest.kt
│       │                   │   │   └── ProductDetailsFragmentTest.kt
│       │                   │   └── loginSignup/
│       │                   │       ├── LoginFragmentTest.kt
│       │                   │       └── SignupFragmentTest.kt
│       │                   └── viewModels/
│       │                       ├── AddEditAddressViewModelTest.kt
│       │                       ├── AddEditProductViewModelTest.kt
│       │                       ├── AuthViewModelTest.kt
│       │                       ├── HomeViewModelTest.kt
│       │                       ├── OrderViewModelTest.kt
│       │                       └── ProductViewModelTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   ├── java/
│       │   │   └── com/
│       │   │       └── vishalgaur/
│       │   │           └── shoppingapp/
│       │   │               ├── ServiceLocator.kt
│       │   │               ├── ShoppingApplication.kt
│       │   │               ├── Utils.kt
│       │   │               ├── data/
│       │   │               │   ├── Product.kt
│       │   │               │   ├── Result.kt
│       │   │               │   ├── ShoppingAppSessionManager.kt
│       │   │               │   ├── UserData.kt
│       │   │               │   ├── source/
│       │   │               │   │   ├── ProductDataSource.kt
│       │   │               │   │   ├── UserDataSource.kt
│       │   │               │   │   ├── local/
│       │   │               │   │   │   ├── ProductsDao.kt
│       │   │               │   │   │   ├── ProductsLocalDataSource.kt
│       │   │               │   │   │   ├── ShoppingAppDatabase.kt
│       │   │               │   │   │   ├── UserDao.kt
│       │   │               │   │   │   └── UserLocalDataSource.kt
│       │   │               │   │   ├── remote/
│       │   │               │   │   │   ├── AuthRemoteDataSource.kt
│       │   │               │   │   │   └── ProductsRemoteDataSource.kt
│       │   │               │   │   └── repository/
│       │   │               │   │       ├── AuthRepoInterface.kt
│       │   │               │   │       ├── AuthRepository.kt
│       │   │               │   │       ├── ProductsRepoInterface.kt
│       │   │               │   │       └── ProductsRepository.kt
│       │   │               │   └── utils/
│       │   │               │       ├── DateTypeConvertors.kt
│       │   │               │       ├── EmailMobileData.kt
│       │   │               │       ├── ListTypeConverter.kt
│       │   │               │       ├── ObjectListTypeConvertor.kt
│       │   │               │       ├── ProductUtils.kt
│       │   │               │       └── Utils.kt
│       │   │               ├── ui/
│       │   │               │   ├── LaunchActivity.kt
│       │   │               │   ├── RecyclerViewPaddingItemDecoration.kt
│       │   │               │   ├── UiUtils.kt
│       │   │               │   ├── home/
│       │   │               │   │   ├── AccountFragment.kt
│       │   │               │   │   ├── AddEditAddressFragment.kt
│       │   │               │   │   ├── AddEditProductFragment.kt
│       │   │               │   │   ├── AddProductImagesAdapter.kt
│       │   │               │   │   ├── AddressAdapter.kt
│       │   │               │   │   ├── AddressFragment.kt
│       │   │               │   │   ├── CartFragment.kt
│       │   │               │   │   ├── CartItemAdapter.kt
│       │   │               │   │   ├── FavoritesFragment.kt
│       │   │               │   │   ├── HomeFragment.kt
│       │   │               │   │   ├── LikedProductAdapter.kt
│       │   │               │   │   ├── MainActivity.kt
│       │   │               │   │   ├── OrderDetailsFragment.kt
│       │   │               │   │   ├── OrderProductsAdapter.kt
│       │   │               │   │   ├── OrderSuccessFragment.kt
│       │   │               │   │   ├── OrdersAdapter.kt
│       │   │               │   │   ├── OrdersFragment.kt
│       │   │               │   │   ├── PayByAdapter.kt
│       │   │               │   │   ├── ProductAdapter.kt
│       │   │               │   │   ├── ProductDetailsFragment.kt
│       │   │               │   │   ├── ProductImagesAdapter.kt
│       │   │               │   │   ├── ProfileFragment.kt
│       │   │               │   │   ├── SelectAddressFragment.kt
│       │   │               │   │   └── SelectPaymentFragment.kt
│       │   │               │   └── loginSignup/
│       │   │               │       ├── LoginFragment.kt
│       │   │               │       ├── LoginSignupActivity.kt
│       │   │               │       ├── LoginSignupBaseFragment.kt
│       │   │               │       ├── OtpActivity.kt
│       │   │               │       └── SignupFragment.kt
│       │   │               └── viewModels/
│       │   │                   ├── AddEditAddressViewModel.kt
│       │   │                   ├── AddEditProductViewModel.kt
│       │   │                   ├── AuthViewModel.kt
│       │   │                   ├── HomeViewModel.kt
│       │   │                   ├── OrderViewModel.kt
│       │   │                   ├── OtpViewModel.kt
│       │   │                   └── ProductViewModel.kt
│       │   └── res/
│       │       ├── drawable/
│       │       │   ├── address_account_drawable.xml
│       │       │   ├── avatar_background.xml
│       │       │   ├── bottom_nav_selector.xml
│       │       │   ├── btn_gradient.xml
│       │       │   ├── card_item_selector.xml
│       │       │   ├── color_radio_normal.xml
│       │       │   ├── color_radio_selected.xml
│       │       │   ├── color_radio_selector.xml
│       │       │   ├── dotted_line_drawable.xml
│       │       │   ├── heart_icon_drawable.xml
│       │       │   ├── ic_add_24.xml
│       │       │   ├── ic_add_48.xml
│       │       │   ├── ic_add_shopping_cart_24.xml
│       │       │   ├── ic_baseline_person_24.xml
│       │       │   ├── ic_baseline_shopping_cart_24.xml
│       │       │   ├── ic_cancel_24.xml
│       │       │   ├── ic_chevron_left_48.xml
│       │       │   ├── ic_delete_24.xml
│       │       │   ├── ic_edit_24.xml
│       │       │   ├── ic_favorite_filled_24.xml
│       │       │   ├── ic_favorite_outlined_24.xml
│       │       │   ├── ic_filled_check_circle_24.xml
│       │       │   ├── ic_filled_library_books_24.xml
│       │       │   ├── ic_filled_location_on_24.xml
│       │       │   ├── ic_filled_logout_24.xml
│       │       │   ├── ic_filled_person_24.xml
│       │       │   ├── ic_filter_alt_24.xml
│       │       │   ├── ic_launcher_background.xml
│       │       │   ├── ic_menu_24.xml
│       │       │   ├── ic_outline_arrow_back_24.xml
│       │       │   ├── ic_outline_email_24.xml
│       │       │   ├── ic_outline_library_books_24.xml
│       │       │   ├── ic_outline_phone_android_24.xml
│       │       │   ├── ic_outlined_home_24.xml
│       │       │   ├── ic_outlined_person_24.xml
│       │       │   ├── ic_outlined_shopping_cart_24.xml
│       │       │   ├── ic_remove_24.xml
│       │       │   ├── ic_remove_shopping_cart_24.xml
│       │       │   ├── ic_search_24.xml
│       │       │   ├── layout_background_rounded_corners.xml
│       │       │   ├── liked_heart_drawable.xml
│       │       │   ├── login_bg_img.xml
│       │       │   ├── orders_account_drawable.xml
│       │       │   ├── person_account_drawable.xml
│       │       │   ├── radio_normal.xml
│       │       │   ├── radio_selected.xml
│       │       │   ├── radio_selector.xml
│       │       │   ├── round_button.xml
│       │       │   ├── round_outline_rect.xml
│       │       │   ├── signout_account_drawable.xml
│       │       │   └── sl_favourite_24dp.xml
│       │       ├── drawable-v24/
│       │       │   └── ic_launcher_foreground.xml
│       │       ├── font/
│       │       │   ├── nunito_sans.xml
│       │       │   └── nunito_sans_extrabold.xml
│       │       ├── layout/
│       │       │   ├── activity_launch.xml
│       │       │   ├── activity_main.xml
│       │       │   ├── activity_otp.xml
│       │       │   ├── activity_signup.xml
│       │       │   ├── add_images_item.xml
│       │       │   ├── cart_list_item.xml
│       │       │   ├── country_list_item.xml
│       │       │   ├── fragment_account.xml
│       │       │   ├── fragment_add_edit_address.xml
│       │       │   ├── fragment_add_edit_product.xml
│       │       │   ├── fragment_address.xml
│       │       │   ├── fragment_cart.xml
│       │       │   ├── fragment_favorites.xml
│       │       │   ├── fragment_home.xml
│       │       │   ├── fragment_login.xml
│       │       │   ├── fragment_order_details.xml
│       │       │   ├── fragment_order_success.xml
│       │       │   ├── fragment_orders.xml
│       │       │   ├── fragment_product_details.xml
│       │       │   ├── fragment_profile.xml
│       │       │   ├── fragment_select_address.xml
│       │       │   ├── fragment_select_payment.xml
│       │       │   ├── fragment_signup.xml
│       │       │   ├── images_item.xml
│       │       │   ├── layout_address_card.xml
│       │       │   ├── layout_circular_loader.xml
│       │       │   ├── layout_home_ad.xml
│       │       │   ├── layout_home_top_app_bar.xml
│       │       │   ├── layout_list_item.xml
│       │       │   ├── layout_loader_card.xml
│       │       │   ├── layout_no_icon_app_bar.xml
│       │       │   ├── layout_order_summary_card.xml
│       │       │   ├── layout_price_card.xml
│       │       │   ├── layout_shipping_card.xml
│       │       │   ├── layout_top_bar.xml
│       │       │   └── products_list_item.xml
│       │       ├── menu/
│       │       │   ├── app_bar_menu.xml
│       │       │   ├── bottom_navigation_menu.xml
│       │       │   ├── home_app_bar_menu.xml
│       │       │   ├── menu_main.xml
│       │       │   └── menu_with_add_only.xml
│       │       ├── mipmap-anydpi-v26/
│       │       │   ├── ic_launcher.xml
│       │       │   └── ic_launcher_round.xml
│       │       ├── navigation/
│       │       │   ├── home_nav_graph.xml
│       │       │   └── signup_nav_graph.xml
│       │       ├── values/
│       │       │   ├── attrs.xml
│       │       │   ├── colors.xml
│       │       │   ├── dimens.xml
│       │       │   ├── font_certs.xml
│       │       │   ├── preloaded_fonts.xml
│       │       │   ├── shapes.xml
│       │       │   ├── strings.xml
│       │       │   ├── styles.xml
│       │       │   ├── themes.xml
│       │       │   └── type.xml
│       │       └── values-night/
│       │           └── themes.xml
│       └── test/
│           └── java/
│               └── com/
│                   └── vishalgaur/
│                       └── shoppingapp/
│                           └── UtilsTest.kt
├── build.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
Condensed preview — 223 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (676K chars).
[
  {
    "path": ".gitignore",
    "chars": 225,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
  },
  {
    "path": ".idea/.gitignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".idea/.name",
    "chars": 12,
    "preview": "Shopping App"
  },
  {
    "path": ".idea/codeStyles/Project.xml",
    "chars": 3724,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <code_scheme name=\"Project\" version=\"173\">\n    <JetCodeStyleSettings>"
  },
  {
    "path": ".idea/codeStyles/codeStyleConfig.xml",
    "chars": 142,
    "preview": "<component name=\"ProjectCodeStyleConfiguration\">\n  <state>\n    <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n "
  },
  {
    "path": ".idea/compiler.xml",
    "chars": 169,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"CompilerConfiguration\">\n    <bytecodeTar"
  },
  {
    "path": ".idea/gradle.xml",
    "chars": 859,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"GradleMigrationSettings\" migrationVersio"
  },
  {
    "path": ".idea/jarRepositories.xml",
    "chars": 1052,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RemoteRepositoriesConfiguration\">\n    <r"
  },
  {
    "path": ".idea/misc.xml",
    "chars": 371,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectRootManager\" version=\"2\" language"
  },
  {
    "path": ".idea/render.experimental.xml",
    "chars": 173,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RenderSettings\">\n    <option name=\"showD"
  },
  {
    "path": ".idea/runConfigurations.xml",
    "chars": 337,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"RunConfigurationProducerService\">\n    <o"
  },
  {
    "path": ".idea/vcs.xml",
    "chars": 180,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"VcsDirectoryMappings\">\n    <mapping dire"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2021 Vishal Gaur\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "README.md",
    "chars": 5042,
    "preview": "# Shopping Android App\nAn e-commerce android application written in Kotlin where users can sell and buy products. \n\n\n## "
  },
  {
    "path": "app/.gitignore",
    "chars": 30,
    "preview": "/build\n\n/google-services.json\n"
  },
  {
    "path": "app/build.gradle",
    "chars": 3869,
    "preview": "plugins {\n    id 'com.android.application'\n    id 'kotlin-android'\n    id 'kotlin-kapt'\n    id 'com.google.gms.google-se"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 750,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/AppDatabaseTest.kt",
    "chars": 4979,
    "preview": "package com.vishalgaur.shoppingapp\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport androidx.r"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/ClickClickableSpan.kt",
    "chars": 1789,
    "preview": "package com.vishalgaur.shoppingapp\n\nimport android.text.SpannableString\nimport android.text.style.ClickableSpan\nimport a"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/ExampleInstrumentedTest.kt",
    "chars": 661,
    "preview": "package com.vishalgaur.shoppingapp\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.a"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/LiveDataTestUtil.kt",
    "chars": 850,
    "preview": "package com.vishalgaur.shoppingapp\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.Observer\nimport java.ut"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/MainCoroutineRule.kt",
    "chars": 828,
    "preview": "package com.vishalgaur.shoppingapp\n\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\n"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/RecyclerViewMatcherUtils.kt",
    "chars": 1607,
    "preview": "package com.vishalgaur.shoppingapp\n\nimport android.view.View\nimport androidx.recyclerview.widget.RecyclerView\nimport and"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/FakeAuthRepository.kt",
    "chars": 6161,
    "preview": "package com.vishalgaur.shoppingapp.data.source\n\nimport android.content.Context\nimport androidx.lifecycle.MutableLiveData"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/FakeProductsDataSource.kt",
    "chars": 3193,
    "preview": "package com.vishalgaur.shoppingapp.data.source\n\nimport android.net.Uri\nimport androidx.core.net.toUri\nimport androidx.li"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/FakeProductsRepository.kt",
    "chars": 3482,
    "preview": "package com.vishalgaur.shoppingapp.data.source\n\nimport android.net.Uri\nimport androidx.core.net.toUri\nimport androidx.li"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/FakeUserDataSource.kt",
    "chars": 4099,
    "preview": "package com.vishalgaur.shoppingapp.data.source\n\nimport com.vishalgaur.shoppingapp.data.Result\nimport com.vishalgaur.shop"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/repository/AuthRepositoryTest.kt",
    "chars": 10297,
    "preview": "package com.vishalgaur.shoppingapp.data.source.repository\n\nimport android.content.Context\nimport androidx.test.core.app."
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/data/source/repository/ProductsRepositoryTest.kt",
    "chars": 8237,
    "preview": "package com.vishalgaur.shoppingapp.data.source.repository\n\nimport androidx.arch.core.executor.testing.InstantTaskExecuto"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/ui/home/HomeFragmentTest.kt",
    "chars": 7659,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.view.View\nimport android.widget.Imag"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/ui/home/ProductDetailsFragmentTest.kt",
    "chars": 2754,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport an"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/ui/loginSignup/LoginFragmentTest.kt",
    "chars": 4178,
    "preview": "package com.vishalgaur.shoppingapp.ui.loginSignup\n\nimport androidx.fragment.app.testing.FragmentScenario\nimport androidx"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/ui/loginSignup/SignupFragmentTest.kt",
    "chars": 6433,
    "preview": "package com.vishalgaur.shoppingapp.ui.loginSignup\n\nimport androidx.fragment.app.testing.FragmentScenario\nimport androidx"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/AddEditAddressViewModelTest.kt",
    "chars": 4945,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/AddEditProductViewModelTest.kt",
    "chars": 3822,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport android.net.Uri\nimport androidx.arch.core.executor.testing.Instant"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/AuthViewModelTest.kt",
    "chars": 5314,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/HomeViewModelTest.kt",
    "chars": 1973,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/OrderViewModelTest.kt",
    "chars": 5420,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport"
  },
  {
    "path": "app/src/androidTest/java/com/vishalgaur/shoppingapp/viewModels/ProductViewModelTest.kt",
    "chars": 2526,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport androidx.arch.core.executor.testing.InstantTaskExecutorRule\nimport"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 1450,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package="
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ServiceLocator.kt",
    "chars": 2748,
    "preview": "package com.vishalgaur.shoppingapp\n\nimport android.content.Context\nimport androidx.annotation.VisibleForTesting\nimport c"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ShoppingApplication.kt",
    "chars": 514,
    "preview": "package com.vishalgaur.shoppingapp\n\nimport android.app.Application\nimport com.vishalgaur.shoppingapp.data.source.reposit"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/Utils.kt",
    "chars": 2061,
    "preview": "package com.vishalgaur.shoppingapp\n\nimport java.util.*\nimport java.util.regex.Pattern\nimport kotlin.math.roundToInt\n\ncon"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/Product.kt",
    "chars": 996,
    "preview": "package com.vishalgaur.shoppingapp.data\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.P"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/Result.kt",
    "chars": 693,
    "preview": "package com.vishalgaur.shoppingapp.data\n\nimport com.vishalgaur.shoppingapp.data.Result.Success\n\n/**\n * A generic class t"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/ShoppingAppSessionManager.kt",
    "chars": 1691,
    "preview": "package com.vishalgaur.shoppingapp.data\n\nimport android.content.Context\nimport android.content.SharedPreferences\n\nclass "
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/UserData.kt",
    "chars": 3418,
    "preview": "package com.vishalgaur.shoppingapp.data\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.P"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/ProductDataSource.kt",
    "chars": 1107,
    "preview": "package com.vishalgaur.shoppingapp.data.source\n\nimport android.net.Uri\nimport androidx.lifecycle.LiveData\nimport android"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/UserDataSource.kt",
    "chars": 1681,
    "preview": "package com.vishalgaur.shoppingapp.data.source\n\nimport com.vishalgaur.shoppingapp.data.Result\nimport com.vishalgaur.shop"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/ProductsDao.kt",
    "chars": 1188,
    "preview": "package com.vishalgaur.shoppingapp.data.source.local\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.Dao\nimport"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/ProductsLocalDataSource.kt",
    "chars": 2710,
    "preview": "package com.vishalgaur.shoppingapp.data.source.local\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.Mutab"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/ShoppingAppDatabase.kt",
    "chars": 1298,
    "preview": "package com.vishalgaur.shoppingapp.data.source.local\n\nimport android.content.Context\nimport androidx.room.Database\nimpor"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/UserDao.kt",
    "chars": 575,
    "preview": "package com.vishalgaur.shoppingapp.data.source.local\n\nimport androidx.room.*\nimport com.vishalgaur.shoppingapp.data.User"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/local/UserLocalDataSource.kt",
    "chars": 6741,
    "preview": "package com.vishalgaur.shoppingapp.data.source.local\n\nimport android.util.Log\nimport com.vishalgaur.shoppingapp.data.Res"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/remote/AuthRemoteDataSource.kt",
    "chars": 11110,
    "preview": "package com.vishalgaur.shoppingapp.data.source.remote\n\nimport android.util.Log\nimport com.google.firebase.firestore.Fiel"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/remote/ProductsRemoteDataSource.kt",
    "chars": 4510,
    "preview": "package com.vishalgaur.shoppingapp.data.source.remote\n\nimport android.net.Uri\nimport android.util.Log\nimport androidx.li"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/repository/AuthRepoInterface.kt",
    "chars": 2158,
    "preview": "package com.vishalgaur.shoppingapp.data.source.repository\n\nimport android.content.Context\nimport androidx.lifecycle.Muta"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/repository/AuthRepository.kt",
    "chars": 12862,
    "preview": "package com.vishalgaur.shoppingapp.data.source.repository\n\nimport android.content.Context\nimport android.util.Log\nimport"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/repository/ProductsRepoInterface.kt",
    "chars": 998,
    "preview": "package com.vishalgaur.shoppingapp.data.source.repository\n\nimport android.net.Uri\nimport androidx.lifecycle.LiveData\nimp"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/source/repository/ProductsRepository.kt",
    "chars": 5836,
    "preview": "package com.vishalgaur.shoppingapp.data.source.repository\n\nimport android.net.Uri\nimport android.util.Log\nimport android"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/utils/DateTypeConvertors.kt",
    "chars": 299,
    "preview": "package com.vishalgaur.shoppingapp.data.utils\n\nimport androidx.room.TypeConverter\nimport java.util.*\n\nclass DateTypeConv"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/utils/EmailMobileData.kt",
    "chars": 168,
    "preview": "package com.vishalgaur.shoppingapp.data.utils\n\ndata class EmailMobileData(\n\tval emails: ArrayList<String> = ArrayList(),"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/utils/ListTypeConverter.kt",
    "chars": 593,
    "preview": "package com.vishalgaur.shoppingapp.data.utils\n\nimport androidx.room.TypeConverter\n\nclass ListTypeConverter {\n\t@TypeConve"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/utils/ObjectListTypeConvertor.kt",
    "chars": 1892,
    "preview": "package com.vishalgaur.shoppingapp.data.utils\n\nimport androidx.room.TypeConverter\nimport com.google.common.reflect.TypeT"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/utils/ProductUtils.kt",
    "chars": 456,
    "preview": "package com.vishalgaur.shoppingapp.data.utils\n\nval ShoeSizes = mapOf(\n\t\"UK4\" to 4,\n\t\"UK5\" to 5,\n\t\"UK6\" to 6,\n\t\"UK7\" to 7"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/data/utils/Utils.kt",
    "chars": 732,
    "preview": "package com.vishalgaur.shoppingapp.data.utils\n\nimport java.util.*\n\nenum class SignUpErrors { NONE, SERR }\n\nenum class Lo"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/LaunchActivity.kt",
    "chars": 1072,
    "preview": "package com.vishalgaur.shoppingapp.ui\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.os.Handler\n"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/RecyclerViewPaddingItemDecoration.kt",
    "chars": 556,
    "preview": "package com.vishalgaur.shoppingapp.ui\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.V"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/UiUtils.kt",
    "chars": 5953,
    "preview": "package com.vishalgaur.shoppingapp.ui\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/AccountFragment.kt",
    "chars": 2637,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.util.L"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/AddEditAddressFragment.kt",
    "chars": 8890,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutI"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/AddEditProductFragment.kt",
    "chars": 9411,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.res.ColorStateList\nimport android.graphics.Color\nimpo"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/AddProductImagesAdapter.kt",
    "chars": 1481,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.Context\nimport android.net.Uri\nimport android.view.La"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/AddressAdapter.kt",
    "chars": 3062,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.Context\nimport android.util.Log\nimport android.util.T"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/AddressFragment.kt",
    "chars": 4362,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutI"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/CartFragment.kt",
    "chars": 7649,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutI"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/CartItemAdapter.kt",
    "chars": 3573,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport and"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/FavoritesFragment.kt",
    "chars": 3473,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutI"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/HomeFragment.kt",
    "chars": 11134,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/LikedProductAdapter.kt",
    "chars": 2772,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.Context\nimport android.graphics.Paint\nimport android."
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/MainActivity.kt",
    "chars": 1919,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.View\nim"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/OrderDetailsFragment.kt",
    "chars": 6691,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutI"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/OrderProductsAdapter.kt",
    "chars": 2064,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport and"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/OrderSuccessFragment.kt",
    "chars": 2360,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.os.CountDownTimer\nimport android.vie"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/OrdersAdapter.kt",
    "chars": 2247,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport and"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/OrdersFragment.kt",
    "chars": 3226,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutI"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/PayByAdapter.kt",
    "chars": 2185,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.annotation.SuppressLint\nimport android.util.Log\nimport androi"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/ProductAdapter.kt",
    "chars": 5089,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.Context\nimport android.graphics.Paint\nimport android."
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/ProductDetailsFragment.kt",
    "chars": 9889,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.annotation.SuppressLint\nimport android.app.Application\nimport"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/ProductImagesAdapter.kt",
    "chars": 1113,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport and"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/ProfileFragment.kt",
    "chars": 1424,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.v"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/SelectAddressFragment.kt",
    "chars": 5068,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutI"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/home/SelectPaymentFragment.kt",
    "chars": 2219,
    "preview": "package com.vishalgaur.shoppingapp.ui.home\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutI"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/loginSignup/LoginFragment.kt",
    "chars": 3241,
    "preview": "package com.vishalgaur.shoppingapp.ui.loginSignup\n\nimport android.text.SpannableString\nimport android.text.Spanned\nimpor"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/loginSignup/LoginSignupActivity.kt",
    "chars": 349,
    "preview": "package com.vishalgaur.shoppingapp.ui.loginSignup\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActiv"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/loginSignup/LoginSignupBaseFragment.kt",
    "chars": 1380,
    "preview": "package com.vishalgaur.shoppingapp.ui.loginSignup\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/loginSignup/OtpActivity.kt",
    "chars": 3445,
    "preview": "package com.vishalgaur.shoppingapp.ui.loginSignup\n\nimport android.app.Application\nimport android.os.Bundle\nimport androi"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/ui/loginSignup/SignupFragment.kt",
    "chars": 3972,
    "preview": "package com.vishalgaur.shoppingapp.ui.loginSignup\n\nimport android.text.SpannableString\nimport android.text.Spanned\nimpor"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/viewModels/AddEditAddressViewModel.kt",
    "chars": 6371,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport android.app.Application\nimport android.util.Log\nimport androidx.an"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/viewModels/AddEditProductViewModel.kt",
    "chars": 6698,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport android.app.Application\nimport android.net.Uri\nimport android.util"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/viewModels/AuthViewModel.kt",
    "chars": 4515,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport android.app.Application\nimport android.util.Log\nimport androidx.li"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/viewModels/HomeViewModel.kt",
    "chars": 11825,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport android.app.Application\nimport android.util.Log\nimport androidx.li"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/viewModels/OrderViewModel.kt",
    "chars": 10769,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport android.app.Application\nimport android.util.Log\nimport androidx.li"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/viewModels/OtpViewModel.kt",
    "chars": 4151,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport android.app.Application\nimport android.util.Log\nimport androidx.fr"
  },
  {
    "path": "app/src/main/java/com/vishalgaur/shoppingapp/viewModels/ProductViewModel.kt",
    "chars": 5335,
    "preview": "package com.vishalgaur.shoppingapp.viewModels\n\nimport android.app.Application\nimport android.util.Log\nimport androidx.li"
  },
  {
    "path": "app/src/main/res/drawable/address_account_drawable.xml",
    "chars": 258,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item"
  },
  {
    "path": "app/src/main/res/drawable/avatar_background.xml",
    "chars": 364,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:inn"
  },
  {
    "path": "app/src/main/res/drawable/bottom_nav_selector.xml",
    "chars": 248,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item a"
  },
  {
    "path": "app/src/main/res/drawable/btn_gradient.xml",
    "chars": 277,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <gradient\n"
  },
  {
    "path": "app/src/main/res/drawable/card_item_selector.xml",
    "chars": 348,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:app=\"h"
  },
  {
    "path": "app/src/main/res/drawable/color_radio_normal.xml",
    "chars": 337,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/color_radio_selected.xml",
    "chars": 959,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- "
  },
  {
    "path": "app/src/main/res/drawable/color_radio_selector.xml",
    "chars": 303,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item a"
  },
  {
    "path": "app/src/main/res/drawable/dotted_line_drawable.xml",
    "chars": 282,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/heart_icon_drawable.xml",
    "chars": 528,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:wi"
  },
  {
    "path": "app/src/main/res/drawable/ic_add_24.xml",
    "chars": 354,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_add_48.xml",
    "chars": 359,
    "preview": "<vector android:height=\"48dp\"\n    android:tint=\"@color/blue_accent_300\"\n    android:viewportHeight=\"24\"\n    android:view"
  },
  {
    "path": "app/src/main/res/drawable/ic_add_shopping_cart_24.xml",
    "chars": 778,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_person_24.xml",
    "chars": 422,
    "preview": "<vector android:height=\"64dp\" android:tint=\"?attr/colorControlNormal\"\n    android:viewportHeight=\"24\" android:viewportWi"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_shopping_cart_24.xml",
    "chars": 704,
    "preview": "<vector android:height=\"64dp\" android:tint=\"?attr/colorControlNormal\"\n    android:viewportHeight=\"24\" android:viewportWi"
  },
  {
    "path": "app/src/main/res/drawable/ic_cancel_24.xml",
    "chars": 717,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_chevron_left_48.xml",
    "chars": 372,
    "preview": "<vector android:height=\"48dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportHeight=\"24\"\n    android:vi"
  },
  {
    "path": "app/src/main/res/drawable/ic_delete_24.xml",
    "chars": 426,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_edit_24.xml",
    "chars": 580,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_favorite_filled_24.xml",
    "chars": 498,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_favorite_outlined_24.xml",
    "chars": 665,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_filled_check_circle_24.xml",
    "chars": 419,
    "preview": "<vector android:height=\"48dp\" android:tint=\"?attr/colorControlNormal\"\n    android:viewportHeight=\"24\" android:viewportWi"
  },
  {
    "path": "app/src/main/res/drawable/ic_filled_library_books_24.xml",
    "chars": 548,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_filled_location_on_24.xml",
    "chars": 490,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_filled_logout_24.xml",
    "chars": 467,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_filled_person_24.xml",
    "chars": 444,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_filter_alt_24.xml",
    "chars": 492,
    "preview": "<vector android:height=\"24dp\"\n    android:tint=\"#AAAAAA\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "chars": 5606,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:wi"
  },
  {
    "path": "app/src/main/res/drawable/ic_menu_24.xml",
    "chars": 383,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_outline_arrow_back_24.xml",
    "chars": 426,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_outline_email_24.xml",
    "chars": 462,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_outline_library_books_24.xml",
    "chars": 551,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_outline_phone_android_24.xml",
    "chars": 457,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_outlined_home_24.xml",
    "chars": 397,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_outlined_person_24.xml",
    "chars": 554,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_outlined_shopping_cart_24.xml",
    "chars": 673,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_remove_24.xml",
    "chars": 336,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_remove_shopping_cart_24.xml",
    "chars": 829,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/ic_search_24.xml",
    "chars": 561,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "app/src/main/res/drawable/layout_background_rounded_corners.xml",
    "chars": 266,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/liked_heart_drawable.xml",
    "chars": 566,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:wi"
  },
  {
    "path": "app/src/main/res/drawable/login_bg_img.xml",
    "chars": 2074,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt"
  },
  {
    "path": "app/src/main/res/drawable/orders_account_drawable.xml",
    "chars": 260,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item"
  },
  {
    "path": "app/src/main/res/drawable/person_account_drawable.xml",
    "chars": 253,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item"
  },
  {
    "path": "app/src/main/res/drawable/radio_normal.xml",
    "chars": 337,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/radio_selected.xml",
    "chars": 301,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/radio_selector.xml",
    "chars": 291,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item a"
  },
  {
    "path": "app/src/main/res/drawable/round_button.xml",
    "chars": 579,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item a"
  },
  {
    "path": "app/src/main/res/drawable/round_outline_rect.xml",
    "chars": 224,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:sha"
  },
  {
    "path": "app/src/main/res/drawable/signout_account_drawable.xml",
    "chars": 253,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item"
  },
  {
    "path": "app/src/main/res/drawable/sl_favourite_24dp.xml",
    "chars": 375,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item a"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "chars": 1702,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    "
  },
  {
    "path": "app/src/main/res/font/nunito_sans.xml",
    "chars": 331,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    app:fontProv"
  },
  {
    "path": "app/src/main/res/font/nunito_sans_extrabold.xml",
    "chars": 380,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<font-family xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        app:font"
  },
  {
    "path": "app/src/main/res/layout/activity_launch.xml",
    "chars": 1026,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 1501,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas"
  },
  {
    "path": "app/src/main/res/layout/activity_otp.xml",
    "chars": 5914,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "app/src/main/res/layout/activity_signup.xml",
    "chars": 982,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas"
  },
  {
    "path": "app/src/main/res/layout/add_images_item.xml",
    "chars": 2943,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schem"
  },
  {
    "path": "app/src/main/res/layout/cart_list_item.xml",
    "chars": 6879,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/country_list_item.xml",
    "chars": 330,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:"
  },
  {
    "path": "app/src/main/res/layout/fragment_account.xml",
    "chars": 5043,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_add_edit_address.xml",
    "chars": 22144,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_add_edit_product.xml",
    "chars": 16398,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schema"
  },
  {
    "path": "app/src/main/res/layout/fragment_address.xml",
    "chars": 3290,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_cart.xml",
    "chars": 3278,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_favorites.xml",
    "chars": 2724,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_home.xml",
    "chars": 2469,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_login.xml",
    "chars": 8842,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:"
  },
  {
    "path": "app/src/main/res/layout/fragment_order_details.xml",
    "chars": 7295,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_order_success.xml",
    "chars": 4088,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/fragment_orders.xml",
    "chars": 2811,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_product_details.xml",
    "chars": 10131,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_profile.xml",
    "chars": 5098,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_select_address.xml",
    "chars": 4443,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_select_payment.xml",
    "chars": 3193,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xm"
  },
  {
    "path": "app/src/main/res/layout/fragment_signup.xml",
    "chars": 12916,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:"
  },
  {
    "path": "app/src/main/res/layout/images_item.xml",
    "chars": 853,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_address_card.xml",
    "chars": 3930,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_circular_loader.xml",
    "chars": 894,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "app/src/main/res/layout/layout_home_ad.xml",
    "chars": 1356,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_home_top_app_bar.xml",
    "chars": 2487,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.appbar.AppBarLayout xmlns:android=\"http://schemas.an"
  },
  {
    "path": "app/src/main/res/layout/layout_list_item.xml",
    "chars": 1686,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_loader_card.xml",
    "chars": 2198,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_no_icon_app_bar.xml",
    "chars": 1008,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.appbar.AppBarLayout xmlns:android=\"http://schemas.an"
  },
  {
    "path": "app/src/main/res/layout/layout_order_summary_card.xml",
    "chars": 6049,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_price_card.xml",
    "chars": 5621,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_shipping_card.xml",
    "chars": 4887,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_top_bar.xml",
    "chars": 1117,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.appbar.AppBarLayout xmlns:android=\"http://schemas.an"
  },
  {
    "path": "app/src/main/res/layout/products_list_item.xml",
    "chars": 6342,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas"
  },
  {
    "path": "app/src/main/res/menu/app_bar_menu.xml",
    "chars": 366,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"h"
  },
  {
    "path": "app/src/main/res/menu/bottom_navigation_menu.xml",
    "chars": 898,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n     "
  },
  {
    "path": "app/src/main/res/menu/home_app_bar_menu.xml",
    "chars": 567,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"h"
  },
  {
    "path": "app/src/main/res/menu/menu_main.xml",
    "chars": 422,
    "preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\""
  },
  {
    "path": "app/src/main/res/menu/menu_with_add_only.xml",
    "chars": 346,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http"
  }
]

// ... and 23 more files (download for full content)

About this extraction

This page contains the full source code of the i-vishi/shopping-android-app GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 223 files (593.6 KB), approximately 159.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!