├── .github └── workflows │ ├── buid-on-push.yml │ ├── publish-on-release.yml │ └── test-with-saucectl-on-push.yml ├── .gitignore ├── .sauce └── config.yml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── libs │ ├── com.kyanogen.signatureview.signature-view.aar │ ├── com.uphyca.creditcardedittext.aar │ └── com.williamww.silky-signature.aar ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── saucelabs │ │ └── mydemoapp │ │ └── android │ │ ├── BaseTest.java │ │ ├── ErrorFlow.java │ │ ├── HappyFlow.java │ │ ├── TestOnlyThis.java │ │ ├── actions │ │ ├── NestingAwareScrollAction.java │ │ └── SideNavClickAction.java │ │ ├── screenshot │ │ └── SauceLabsCustomScreenshot.java │ │ └── view │ │ └── activities │ │ ├── DashboardToCheckout.java │ │ ├── LoginTest.java │ │ ├── ScreenRotationTest.java │ │ ├── VisualTest.java │ │ └── WebViewTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── saucelabs │ │ │ └── mydemoapp │ │ │ └── android │ │ │ ├── Config.java │ │ │ ├── DeviceVitalsDemo.java │ │ │ ├── MyApplication.java │ │ │ ├── database │ │ │ ├── AppDao.java │ │ │ ├── AppDatabase.java │ │ │ └── AppExecutors.java │ │ │ ├── interfaces │ │ │ ├── OnDialogCallBack.java │ │ │ └── OnItemClickListener.java │ │ │ ├── model │ │ │ ├── CartItemModel.java │ │ │ ├── CheckoutInfo.java │ │ │ ├── ColorModel.java │ │ │ ├── ColorModelConverters.java │ │ │ ├── MenuItem.java │ │ │ ├── ProductModel.java │ │ │ └── User.java │ │ │ ├── utils │ │ │ ├── Constants.java │ │ │ ├── DatabaseRepository.java │ │ │ ├── GridSpacingItemDecoration.java │ │ │ ├── Methods.java │ │ │ ├── Network.java │ │ │ ├── QrCodeAnalyzer.java │ │ │ ├── SingletonClass.java │ │ │ ├── base │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── BaseAdapter.java │ │ │ │ ├── BaseFragment.java │ │ │ │ ├── BaseModel.java │ │ │ │ └── BaseViewModel.java │ │ │ └── preference │ │ │ │ └── PrefManager.java │ │ │ ├── view │ │ │ ├── activities │ │ │ │ ├── DebugCrashActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── SplashActivity.java │ │ │ │ └── VirtualUsbActivity.java │ │ │ ├── adapters │ │ │ │ ├── CartItemAdapter.java │ │ │ │ ├── ColorsAdapter.java │ │ │ │ ├── MenuAdapter.java │ │ │ │ └── ProductsAdapter.java │ │ │ └── fragments │ │ │ │ ├── AboutFragment.java │ │ │ │ ├── BiometricFragment.java │ │ │ │ ├── CartFragment.java │ │ │ │ ├── CheckoutCompleteFragment.java │ │ │ │ ├── CheckoutFragment.java │ │ │ │ ├── CheckoutInfoFragment.java │ │ │ │ ├── DrawingFragment.java │ │ │ │ ├── LocationFragment.java │ │ │ │ ├── LoginFragment.java │ │ │ │ ├── PlaceOrderFragment.java │ │ │ │ ├── ProductCatalogFragment.java │ │ │ │ ├── ProductDetailFragment.java │ │ │ │ ├── QRFragment.java │ │ │ │ ├── WebAddressFragment.java │ │ │ │ └── WebViewFragment.java │ │ │ └── viewModel │ │ │ ├── ProductCatalogViewModel.java │ │ │ ├── ProductCatalogViewModelFactory.java │ │ │ ├── ProductDetailViewModel.java │ │ │ ├── ProductDetailViewModelFactory.java │ │ │ ├── SplashViewModel.java │ │ │ └── SplashViewModelFactory.java │ └── res │ │ ├── anim │ │ └── rotate.xml │ │ ├── drawable-xxhdpi │ │ └── loading_icon.png │ │ ├── drawable │ │ ├── about_drawable.xml │ │ ├── alert.png │ │ ├── back_button.xml │ │ ├── bg_my_list_button.xml │ │ ├── bot_graphic.png │ │ ├── bottom_radius_view.xml │ │ ├── cart.png │ │ ├── cart_number_bg.xml │ │ ├── circle_view.xml │ │ ├── cursor_drawable.xml │ │ ├── drawing_bg.png │ │ ├── edit_bg_grey.xml │ │ ├── edit_bg_red.xml │ │ ├── filled_grey_circle.xml │ │ ├── gradient_bg.xml │ │ ├── grey_circle.xml │ │ ├── ic_black_circle.png │ │ ├── ic_black_circle_side.xml │ │ ├── ic_blue_circle.png │ │ ├── ic_blue_circle_side.xml │ │ ├── ic_done.xml │ │ ├── ic_fb.png │ │ ├── ic_gray_circle.png │ │ ├── ic_gray_circle_side.xml │ │ ├── ic_green_circle.png │ │ ├── ic_green_circle_side.xml │ │ ├── ic_horse.png │ │ ├── ic_in.png │ │ ├── ic_menu.xml │ │ ├── ic_minus.png │ │ ├── ic_plus.png │ │ ├── ic_product1.jpg │ │ ├── ic_product2.jpg │ │ ├── ic_product2_blue.jpg │ │ ├── ic_product2_green.jpg │ │ ├── ic_product2_red.jpg │ │ ├── ic_product2_yellow.jpg │ │ ├── ic_product3.jpg │ │ ├── ic_product3_blue.jpg │ │ ├── ic_product3_brown.jpg │ │ ├── ic_product3_green.jpg │ │ ├── ic_product3_pink.jpg │ │ ├── ic_product3_red.jpg │ │ ├── ic_product4.jpg │ │ ├── ic_product4_green.jpg │ │ ├── ic_product4_orange.jpg │ │ ├── ic_product4_red.jpg │ │ ├── ic_product4_violet.jpg │ │ ├── ic_product4_yellow.jpg │ │ ├── ic_product5.jpg │ │ ├── ic_product5_pink.jpg │ │ ├── ic_product5_purple.jpg │ │ ├── ic_product5_turquoise.jpg │ │ ├── ic_product5_yellow.jpg │ │ ├── ic_product6.jpg │ │ ├── ic_red_circle.png │ │ ├── ic_red_circle_side.xml │ │ ├── ic_selected_star.png │ │ ├── ic_splash_logo.png │ │ ├── ic_twitter.png │ │ ├── ic_unselected_start.png │ │ ├── icon_touch_id.png │ │ ├── item_bottom_view_bg.xml │ │ ├── login_button_bg.xml │ │ ├── mastercard.png │ │ ├── my_button_background_selector.xml │ │ ├── not_pressed.xml │ │ ├── pressed.xml │ │ ├── price_asc.png │ │ ├── price_des.png │ │ ├── question.png │ │ ├── recipe_selction_bg.xml │ │ ├── rounded_corner_add_bg.xml │ │ ├── rounded_fields_bg.xml │ │ ├── rounded_fields_bg_orange.xml │ │ ├── sort_asc.png │ │ ├── sort_des.png │ │ ├── tab_background.xml │ │ ├── tab_background_selected.xml │ │ ├── tab_layout_background.xml │ │ ├── tab_layout_selector.xml │ │ ├── text_bg.xml │ │ ├── textview_states.xml │ │ ├── title.png │ │ ├── un_selected_dot.xml │ │ ├── unselected_radio_bg.xml │ │ ├── vector.png │ │ └── visa.png │ │ ├── font │ │ ├── bold_font.ttf │ │ ├── light_font.ttf │ │ ├── regular_font.otf │ │ └── regular_font_.ttf │ │ ├── layout │ │ ├── activity_debug_crash.xml │ │ ├── activity_debug_feedback.xml │ │ ├── activity_main.xml │ │ ├── activity_splash.xml │ │ ├── activity_virtual_usb.xml │ │ ├── dialog_review.xml │ │ ├── fragment_about.xml │ │ ├── fragment_biometric.xml │ │ ├── fragment_cart.xml │ │ ├── fragment_checkout.xml │ │ ├── fragment_checkout_complete.xml │ │ ├── fragment_checkout_info.xml │ │ ├── fragment_drawing.xml │ │ ├── fragment_location.xml │ │ ├── fragment_login.xml │ │ ├── fragment_place_order.xml │ │ ├── fragment_product_catalog.xml │ │ ├── fragment_product_detail.xml │ │ ├── fragment_qr.xml │ │ ├── fragment_web_address.xml │ │ ├── fragment_web_view.xml │ │ ├── item_color.xml │ │ ├── item_my_cart.xml │ │ ├── item_products.xml │ │ ├── menu_header_layout.xml │ │ ├── menu_item.xml │ │ ├── navigation_layout.xml │ │ ├── ratting_layout.xml │ │ └── sort_dialog.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── app_style.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── saucelabs │ └── mydemoapp │ └── android │ └── ExampleUnitTest.java ├── build.gradle ├── docs └── assets │ └── qr-code.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/buid-on-push.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | timeout-minutes: 10 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | 13 | - name: Setup Java 14 | uses: actions/setup-java@v1 15 | with: 16 | java-version: 11 17 | 18 | - name: Setup Android SDK 19 | uses: android-actions/setup-android@v2 20 | 21 | - name: Compile 22 | run: | 23 | ./gradlew app:build 24 | shell: bash -------------------------------------------------------------------------------- /.github/workflows/publish-on-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish APK 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish: 10 | timeout-minutes: 10 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | fail-fast: true 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | 19 | - name: Setup java 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 11 23 | 24 | - name: Setup Android SDK 25 | uses: android-actions/setup-android@v2 26 | 27 | - name: Detect version from release tag 28 | run: | 29 | echo "CI_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 30 | shell: bash 31 | 32 | - name: Set app version and bump version code 33 | uses: chkfung/android-version-actions@v1.1 34 | with: 35 | gradlePath: app/build.gradle 36 | versionCode: ${{ github.run_number }} 37 | versionName: ${{ env.CI_TAG }} 38 | 39 | - name: Compile APK 40 | run: | 41 | ./gradlew app:assembleDebug 42 | ./gradlew app:assembleDebugAndroidTest 43 | mv app/build/outputs/apk/debug/app-debug.apk app/build/outputs/apk/debug/mda-${{ env.CI_TAG }}-${{ github.run_number }}.apk 44 | mv app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk app/build/outputs/apk/debug/mda-androidTest-${{ env.CI_TAG }}-${{ github.run_number }}.apk 45 | shell: bash 46 | 47 | - name: Upload release artifacts 48 | uses: softprops/action-gh-release@v1 49 | with: 50 | name: Version ${{ env.CI_TAG }} 51 | tag_name: ${{ env.CI_TAG }} 52 | fail_on_unmatched_files: true 53 | prerelease: false 54 | files: | 55 | app/build/outputs/apk/debug/mda-${{ env.CI_TAG }}-${{ github.run_number }}.apk 56 | app/build/outputs/apk/debug/mda-androidTest-${{ env.CI_TAG }}-${{ github.run_number }}.apk 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test-with-saucectl-on-push.yml: -------------------------------------------------------------------------------- 1 | name: Test with saucectl 2 | 3 | on: [push] 4 | 5 | env: 6 | SAUCE_USERNAME: ${{secrets.SAUCE_USERNAME}} 7 | SAUCE_ACCESS_KEY: ${{secrets.SAUCE_ACCESS_KEY}} 8 | 9 | jobs: 10 | main: 11 | timeout-minutes: 20 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup Java 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: 11 22 | 23 | - name: Setup Android SDK 24 | uses: android-actions/setup-android@v2 25 | 26 | - name: Compile 27 | run: | 28 | ./gradlew app:assembleDebug 29 | ./gradlew app:assembleDebugAndroidTest 30 | shell: bash 31 | 32 | - name: Test espresso with saucectl 33 | uses: saucelabs/saucectl-run-action@v1 34 | with: 35 | testing-environment: "" 36 | concurrency: 10 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | 10 | # Compiled Java class files 11 | *.class 12 | 13 | # Compiled Python bytecode 14 | *.py[cod] 15 | 16 | # Log files 17 | *.log 18 | 19 | # Package files 20 | *.jar 21 | 22 | # Maven 23 | target/ 24 | dist/ 25 | 26 | # JetBrains IDE 27 | .idea/ 28 | 29 | # Unit test reports 30 | TEST*.xml 31 | 32 | # Generated by MacOS 33 | .DS_Store 34 | 35 | # Generated by Windows 36 | Thumbs.db 37 | 38 | # Applications 39 | *.app 40 | *.exe 41 | *.war 42 | 43 | # Large media files 44 | *.mp4 45 | *.tiff 46 | *.avi 47 | *.flv 48 | *.mov 49 | *.wmv 50 | 51 | # Android Studio 52 | local.properties 53 | .gradle/ 54 | build/* 55 | app/build/* 56 | 57 | # saucectl 58 | artifacts 59 | 60 | -------------------------------------------------------------------------------- /.sauce/config.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1alpha 2 | kind: espresso 3 | sauce: 4 | region: us-west-1 5 | # Controls how many suites are executed at the same time (sauce test env only). 6 | concurrency: 2 7 | metadata: 8 | name: Testing My Demo App Android with espresso 9 | tags: 10 | - e2e 11 | - release team 12 | - other tag 13 | build: Release $CI_COMMIT_SHORT_SHA 14 | espresso: 15 | app: ./app/build/outputs/apk/debug/app-debug.apk 16 | testApp: ./app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk 17 | suites: 18 | - name: "Main suite" 19 | emulators: 20 | - name: Android GoogleAPI Emulator 21 | platformVersions: 22 | - "14.0" 23 | devices: 24 | - name: "Google.*" 25 | platformVersion: "14" 26 | - name: "Samsung.*" 27 | platformVersion: "13" 28 | testOptions: 29 | visualCheck: $VISUAL_CHECK 30 | 31 | 32 | # Controls what artifacts to fetch when the suite on Sauce Cloud has finished. 33 | artifacts: 34 | download: 35 | when: always 36 | match: 37 | - junit.xml 38 | directory: ./artifacts/ 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # my-demo-app-android 2 | 3 | *My Demo App* is a... demo app! 4 | It was built by the Sauce Labs team to showcase product capabilities of the Sauce Labs mobile devices cloud, The Sauce Labs mobile beta testing platform, TestFairy, and more products and technologies that will be added to this project soon. 5 | 6 | This app is part of a set of demo apps. 7 | 8 | [My Demo App - Android](https://github.com/saucelabs/my-demo-app-android) 9 | 10 | [My Demo App - iOS](https://github.com/saucelabs/my-demo-app-ios) 11 | 12 | ### QR code scanner 13 | 14 | This app has a QR code scanner. 15 | You can find it in the menu under the option "QR CODE SCANNER". 16 | This page opens the camera (you first need to allow the app to use the camera) which can be used to scan a QR Code. 17 | If the QR code holds an URL it will automatically open it in a browser. The [following image](./docs/assets/qr-code.png) 18 | can be used to demo this option. 19 | 20 | ![QR Code](./docs/assets/qr-code.png) 21 | 22 | ## Publish 23 | 24 | To publish a new version, create a release with a valid semver tag name. A CI workflow will handle setting the app version name/code and upload the APK into the release. 25 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/libs/com.kyanogen.signatureview.signature-view.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/libs/com.kyanogen.signatureview.signature-view.aar -------------------------------------------------------------------------------- /app/libs/com.uphyca.creditcardedittext.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/libs/com.uphyca.creditcardedittext.aar -------------------------------------------------------------------------------- /app/libs/com.williamww.silky-signature.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/libs/com.williamww.silky-signature.aar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/saucelabs/mydemoapp/android/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android; 2 | 3 | import static androidx.test.espresso.Espresso.onView; 4 | import static androidx.test.espresso.assertion.ViewAssertions.matches; 5 | import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; 6 | 7 | import android.view.View; 8 | 9 | import org.hamcrest.Matcher; 10 | 11 | public class BaseTest { 12 | 13 | protected void waitView(Matcher viewMatcher) { 14 | waitView(viewMatcher, 5000); 15 | } 16 | 17 | protected void waitView(Matcher viewMatcher, long millis) { 18 | long start = System.currentTimeMillis(); 19 | while (System.currentTimeMillis() - start <= millis) { 20 | try { 21 | onView(viewMatcher).check(matches(isDisplayed())); 22 | break; 23 | } catch (Throwable t) { 24 | } 25 | 26 | try { 27 | Thread.sleep(500); 28 | } catch (InterruptedException e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/saucelabs/mydemoapp/android/ErrorFlow.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD, ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ErrorFlow { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/saucelabs/mydemoapp/android/HappyFlow.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target({ElementType.METHOD, ElementType.TYPE}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface HappyFlow { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/saucelabs/mydemoapp/android/TestOnlyThis.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | // Use the command below to run test classes or test methods marked by this annotation only 9 | // 10 | // ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation=com.saucelabs.mydemoapp.android.TestOnlyThis 11 | @Target({ElementType.METHOD, ElementType.TYPE}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface TestOnlyThis { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/saucelabs/mydemoapp/android/actions/NestingAwareScrollAction.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.actions; 2 | 3 | import android.view.View; 4 | import android.widget.HorizontalScrollView; 5 | import android.widget.ScrollView; 6 | 7 | import androidx.core.widget.NestedScrollView; 8 | import androidx.test.espresso.UiController; 9 | import androidx.test.espresso.ViewAction; 10 | import androidx.test.espresso.action.ScrollToAction; 11 | import androidx.test.espresso.matcher.ViewMatchers; 12 | 13 | import org.hamcrest.Matcher; 14 | 15 | import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom; 16 | import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA; 17 | import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; 18 | import static org.hamcrest.core.AllOf.allOf; 19 | import static org.hamcrest.core.AnyOf.anyOf; 20 | 21 | /** 22 | * Scroll action for nested scrollables 23 | */ 24 | public class NestingAwareScrollAction implements ViewAction { 25 | @Override 26 | public Matcher getConstraints() { 27 | return allOf(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE), isDescendantOfA(anyOf( 28 | isAssignableFrom(ScrollView.class), 29 | isAssignableFrom(HorizontalScrollView.class), 30 | isAssignableFrom(NestedScrollView.class))) 31 | ); 32 | } 33 | 34 | @Override 35 | public String getDescription() { 36 | return null; 37 | } 38 | 39 | @Override 40 | public void perform(UiController uiController, View view) { 41 | new ScrollToAction().perform(uiController, view); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/saucelabs/mydemoapp/android/actions/SideNavClickAction.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.actions; 2 | 3 | import android.view.View; 4 | 5 | import androidx.test.espresso.UiController; 6 | import androidx.test.espresso.ViewAction; 7 | 8 | import com.saucelabs.mydemoapp.android.R; 9 | 10 | import org.hamcrest.Matcher; 11 | 12 | public class SideNavClickAction implements ViewAction { 13 | @Override 14 | public Matcher getConstraints() { 15 | return null; 16 | } 17 | 18 | @Override 19 | public String getDescription() { 20 | return "Click on a child view with specified id."; 21 | } 22 | 23 | @Override 24 | public void perform(UiController uiController, View view) { 25 | View v = view.findViewById(R.id.itemTV); 26 | v.performClick(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/saucelabs/mydemoapp/android/screenshot/SauceLabsCustomScreenshot.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.screenshot; 2 | 3 | import android.graphics.Bitmap; 4 | import android.os.Environment; 5 | import android.util.Log; 6 | 7 | import androidx.test.runner.screenshot.BasicScreenCaptureProcessor; 8 | import androidx.test.runner.screenshot.ScreenCapture; 9 | import androidx.test.runner.screenshot.Screenshot; 10 | 11 | import com.saucelabs.mydemoapp.android.Config; 12 | 13 | import java.io.File; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | 16 | public class SauceLabsCustomScreenshot { 17 | 18 | private static final AtomicInteger screenshotId = new AtomicInteger(1); 19 | 20 | public static void capture(String screenshotName) { 21 | try { 22 | screenshotName = String.format("%04d-%s", screenshotId.getAndIncrement(), screenshotName); 23 | 24 | ScreenCapture capture = Screenshot.capture(); 25 | capture.setFormat(Bitmap.CompressFormat.PNG); 26 | capture.setName(screenshotName); 27 | 28 | SauceLabsScreenCaptureProcessor processor = new SauceLabsScreenCaptureProcessor(); 29 | processor.process(capture); 30 | 31 | String savedScreenshotPath = new File(processor.getPathForFile(screenshotName) + ".png").getPath(); 32 | 33 | Log.i(Config.TAG, String.format("Screenshot saved to %s", savedScreenshotPath)); 34 | } catch (Exception e) { 35 | Log.e(Config.TAG, "Failed to capture screenshot", e); 36 | } 37 | } 38 | 39 | private static class SauceLabsScreenCaptureProcessor extends BasicScreenCaptureProcessor { 40 | 41 | private static final String SAUCE_LABS_CUSTOM_SCREENSHOTS = "sauce_labs_custom_screenshots"; 42 | 43 | public SauceLabsScreenCaptureProcessor() { 44 | super(); 45 | 46 | File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 47 | File screenshotsDir = new File(downloadsDir, SAUCE_LABS_CUSTOM_SCREENSHOTS); 48 | this.mDefaultScreenshotPath = screenshotsDir; 49 | } 50 | 51 | public String getPathForFile(String name) { 52 | File file = new File(mDefaultScreenshotPath, getFilename(name)); 53 | return file.getAbsolutePath(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/saucelabs/mydemoapp/android/view/activities/ScreenRotationTest.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.activities; 2 | 3 | import static androidx.test.espresso.Espresso.onView; 4 | import static androidx.test.espresso.assertion.ViewAssertions.matches; 5 | import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; 6 | import static androidx.test.espresso.matcher.ViewMatchers.isRoot; 7 | import static androidx.test.espresso.matcher.ViewMatchers.withId; 8 | 9 | import android.content.pm.ActivityInfo; 10 | import android.content.res.Configuration; 11 | import android.os.SystemClock; 12 | import static org.junit.Assert.assertEquals; 13 | 14 | import androidx.test.espresso.ViewAssertion; 15 | import androidx.test.ext.junit.rules.ActivityScenarioRule; 16 | 17 | import com.saucelabs.mydemoapp.android.BaseTest; 18 | import com.saucelabs.mydemoapp.android.R; 19 | 20 | import org.junit.Before; 21 | import org.junit.Rule; 22 | import org.junit.Test; 23 | 24 | public class ScreenRotationTest extends BaseTest { 25 | @Rule 26 | public ActivityScenarioRule activityRule = new ActivityScenarioRule<>(MainActivity.class); 27 | 28 | @Before 29 | public void setUp(){ 30 | 31 | } 32 | 33 | @Test 34 | public void screenOrientationTest() { 35 | // Splash screen 36 | waitView(withId(R.id.menuIV)); 37 | 38 | // Verify the screen orientation is portrait 39 | onView(isRoot()).check(matchesOrientation(Configuration.ORIENTATION_PORTRAIT)); 40 | 41 | // set screen orientation to landscape 42 | activityRule.getScenario().onActivity(activity -> { 43 | activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 44 | }); 45 | // Pause for 2 seconds for demo purpose 46 | SystemClock.sleep(2000); 47 | // Verify the screen orientation is landscape 48 | onView(isRoot()).check(matchesOrientation(Configuration.ORIENTATION_LANDSCAPE)); 49 | 50 | // set screen orientation to portrait again 51 | activityRule.getScenario().onActivity(activity -> { 52 | activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 53 | }); 54 | // Pause for 2 seconds for demo purpose 55 | SystemClock.sleep(2000); 56 | // Verify the screen orientation is portrait 57 | onView(isRoot()).check(matchesOrientation(Configuration.ORIENTATION_PORTRAIT)); 58 | } 59 | 60 | public static ViewAssertion matchesOrientation(int orientation) { 61 | return (view, noViewFoundException) -> { 62 | if (noViewFoundException != null) { 63 | throw noViewFoundException; 64 | } 65 | 66 | int actualOrientation = view.getContext().getResources().getConfiguration().orientation; 67 | assertEquals(orientation, actualOrientation); 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/saucelabs/mydemoapp/android/view/activities/WebViewTest.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.activities; 2 | 3 | import androidx.lifecycle.Lifecycle; 4 | import androidx.test.espresso.contrib.RecyclerViewActions; 5 | import androidx.test.ext.junit.rules.ActivityScenarioRule; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import com.saucelabs.mydemoapp.android.BaseTest; 9 | import com.saucelabs.mydemoapp.android.ErrorFlow; 10 | import com.saucelabs.mydemoapp.android.HappyFlow; 11 | import com.saucelabs.mydemoapp.android.R; 12 | import com.saucelabs.mydemoapp.android.actions.SideNavClickAction; 13 | import com.saucelabs.mydemoapp.android.screenshot.SauceLabsCustomScreenshot; 14 | 15 | import org.junit.After; 16 | import org.junit.Before; 17 | import org.junit.Rule; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | 21 | import static androidx.test.espresso.Espresso.onView; 22 | import static androidx.test.espresso.action.ViewActions.click; 23 | import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; 24 | import static androidx.test.espresso.action.ViewActions.scrollTo; 25 | import static androidx.test.espresso.action.ViewActions.typeText; 26 | import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; 27 | import static androidx.test.espresso.assertion.ViewAssertions.matches; 28 | import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; 29 | import static androidx.test.espresso.matcher.ViewMatchers.withId; 30 | import static androidx.test.espresso.matcher.ViewMatchers.withText; 31 | 32 | @RunWith(AndroidJUnit4.class) 33 | public class WebViewTest extends BaseTest { 34 | 35 | String url; 36 | 37 | @Rule 38 | public ActivityScenarioRule activityRule 39 | = new ActivityScenarioRule<>(SplashActivity.class); 40 | 41 | @Before 42 | public void setUp(){ 43 | url="https://www.google.com"; 44 | } 45 | 46 | @After 47 | public void cleanup() { 48 | 49 | } 50 | 51 | @Test 52 | @ErrorFlow 53 | public void withoutUrlTest() throws InterruptedException { 54 | waitView(withId(R.id.menuIV)); 55 | 56 | SauceLabsCustomScreenshot.capture("launch-screen"); 57 | 58 | // Open Drawer to click on navigation. 59 | onView(withId(R.id.menuIV)).perform(click()); 60 | 61 | onView(withId(R.id.menuRV)).perform( 62 | RecyclerViewActions.actionOnItemAtPosition(1, new SideNavClickAction())); 63 | 64 | onView(withId(R.id.goBtn)).perform(click()); 65 | onView(withId(R.id.fragment_container)).check(matches((isDisplayed()))); 66 | 67 | SauceLabsCustomScreenshot.capture("error-screen"); 68 | 69 | onView(withText("Please provide a correct https url.")).check(matches(isDisplayed())); 70 | } 71 | 72 | @Test 73 | @HappyFlow 74 | public void webViewTest() { 75 | waitView(withId(R.id.menuIV)); 76 | 77 | // Open Drawer to click on navigation. 78 | onView(withId(R.id.menuIV)).perform(click()); 79 | 80 | onView(withId(R.id.menuRV)).perform( 81 | RecyclerViewActions.actionOnItemAtPosition(1, new SideNavClickAction())); 82 | 83 | onView(withId(R.id.urlET)) 84 | .perform(typeText(url),closeSoftKeyboard()); 85 | onView(withId(R.id.goBtn)) 86 | .perform(click()); 87 | 88 | onView(withText("Please provide a correct https url.")).check(doesNotExist()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | 47 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/Config.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android; 2 | 3 | public class Config { 4 | public static final String TAG = "SauceLabs"; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/DeviceVitalsDemo.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android; 2 | 3 | import android.util.Log; 4 | 5 | import com.saucelabs.mydemoapp.android.utils.Network; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Timer; 10 | import java.util.TimerTask; 11 | import java.util.UUID; 12 | 13 | public class DeviceVitalsDemo { 14 | 15 | private final TimerTask fetchJavascript = new TimerTask() { 16 | 17 | private static final String url = "https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"; 18 | 19 | @Override 20 | public void run() { 21 | Network.fetch(url); 22 | } 23 | }; 24 | 25 | private final TimerTask fetchImage = new TimerTask() { 26 | 27 | private static final String url = "https://my-demo-app.net/logo.png"; 28 | 29 | @Override 30 | public void run() { 31 | Network.fetch(url); 32 | } 33 | }; 34 | 35 | private final TimerTask postAnalytics = new TimerTask() { 36 | 37 | private static final String url = "https://my-demo-app.net/api/analytics/collect/"; 38 | 39 | @Override 40 | public void run() { 41 | String randomness = ""; 42 | while (randomness.length() < 4096) { 43 | String chunk = UUID.randomUUID().toString().replace("-", ""); 44 | randomness = randomness.concat(chunk); 45 | } 46 | 47 | Log.v(Config.TAG, randomness); 48 | Network.post(url, randomness, "application/text"); 49 | } 50 | }; 51 | 52 | private final TimerTask memorySawtooth = new TimerTask() { 53 | 54 | private int step = 0; 55 | private final List items = new ArrayList<>(); 56 | 57 | private static final int ONE_MEGABYTE = 1024 * 1024; 58 | 59 | @Override 60 | public void run() { 61 | if (step < 32) { 62 | // [0 .. 31] we add one megabyte 63 | step++; 64 | items.add(new Byte[ONE_MEGABYTE]); 65 | } else if (step < 64) { 66 | // [ 32 .. 63] we remove one megabyte 67 | step++; 68 | items.remove(0); 69 | } else if (step == 64) { 70 | step = 0; 71 | } 72 | 73 | System.gc(); 74 | } 75 | }; 76 | 77 | public void kickstart() { 78 | Timer timer = new Timer(); 79 | timer.schedule(fetchJavascript, 2000); 80 | // timer.schedule(fetchImage, 4000); 81 | timer.schedule(postAnalytics, 5000); 82 | timer.schedule(memorySawtooth, 1000, 500); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.util.*; 5 | 6 | import android.content.Context; 7 | import android.os.Build; 8 | import android.util.Log; 9 | 10 | import com.saucelabs.mydemoapp.android.utils.Network; 11 | import com.saucelabs.mydemoapp.android.utils.SingletonClass; 12 | 13 | import backtraceio.library.BacktraceClient; 14 | import backtraceio.library.BacktraceCredentials; 15 | import backtraceio.library.models.BacktraceExceptionHandler; 16 | 17 | public class MyApplication extends android.app.Application { 18 | 19 | public static MyApplication instance; 20 | public static BacktraceClient backtraceClient; 21 | 22 | @Override 23 | public void onCreate() { 24 | super.onCreate(); 25 | instance = this; 26 | SingletonClass.getInstance(); 27 | 28 | initializeBacktrace(); 29 | 30 | DeviceVitalsDemo demo = new DeviceVitalsDemo(); 31 | demo.kickstart(); 32 | } 33 | 34 | @Override 35 | public Context getApplicationContext() { 36 | return super.getApplicationContext(); 37 | } 38 | 39 | public static MyApplication getInstance() { 40 | return instance; 41 | } 42 | 43 | private void initializeBacktrace() { 44 | /* 45 | BacktraceCredentials credentials = new BacktraceCredentials( 46 | "", 47 | "" 48 | ); 49 | 50 | BacktraceClient backtraceClient = new BacktraceClient(getApplicationContext(), credentials); 51 | BacktraceExceptionHandler.enable(backtraceClient); 52 | 53 | MyApplication.backtraceClient = backtraceClient; 54 | */ 55 | } 56 | 57 | private static String getRandomUserId() { 58 | Random random = new Random(); 59 | String[] names = new String[]{ 60 | "oliver", "william", "james", "benjamin", "henry", "diego", "alexander", "guy", 61 | "michael", "daniel", "jacob", "roy", "jonathan", "olivia", "charlotte", "sophia", 62 | "sarah", "isabella", "evelyn", "harper", "camila", "gianna", "abigail", "ella" 63 | }; 64 | 65 | return names[Math.abs(random.nextInt()) % names.length] + "@example.com"; 66 | } 67 | 68 | private void fetchToS() { 69 | Network.fetch("https://my-demo-app.net/terms-of-service"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/database/AppDao.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.database; 2 | 3 | 4 | import androidx.room.Dao; 5 | import androidx.room.Delete; 6 | import androidx.room.Insert; 7 | import androidx.room.Query; 8 | import androidx.room.Update; 9 | 10 | import com.saucelabs.mydemoapp.android.model.ProductModel; 11 | import com.saucelabs.mydemoapp.android.model.User; 12 | 13 | import java.util.List; 14 | 15 | @Dao 16 | public interface AppDao { 17 | @Query("SELECT * FROM person ORDER BY ID") 18 | List loadAllPersons(); 19 | 20 | @Insert 21 | void insertPerson(User person); 22 | 23 | @Update 24 | void updatePerson(User person); 25 | 26 | @Delete 27 | void delete(User person); 28 | 29 | @Query("SELECT * FROM person WHERE id = :id") 30 | User loadPersonById(int id); 31 | 32 | @Query("SELECT * FROM Product ORDER BY ID") 33 | List getAllProducts(); 34 | 35 | @Query("SELECT * FROM Product ORDER BY price ASC") 36 | List getPersonsSortByAscPrice(); 37 | 38 | @Query("SELECT * FROM Product ORDER BY price DESC") 39 | List getPersonsSortByDescPrice(); 40 | 41 | @Query("SELECT * FROM Product ORDER BY title ASC") 42 | List getPersonsSortByAscName(); 43 | 44 | @Query("SELECT * FROM Product ORDER BY title DESC") 45 | List getPersonsSortByDescName(); 46 | 47 | 48 | @Insert 49 | void insertProduct(ProductModel person); 50 | 51 | @Insert 52 | void insertProduct(List person); 53 | 54 | @Query("SELECT * FROM Product WHERE id = :id") 55 | ProductModel getProduct(int id); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/database/AppDatabase.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.database; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import androidx.room.Database; 7 | import androidx.room.Room; 8 | import androidx.room.RoomDatabase; 9 | 10 | import com.saucelabs.mydemoapp.android.model.ProductModel; 11 | import com.saucelabs.mydemoapp.android.model.User; 12 | 13 | @Database(entities = {User.class, ProductModel.class}, version = 1, exportSchema = false) 14 | public abstract class AppDatabase extends RoomDatabase { 15 | private static final String LOG_TAG = AppDatabase.class.getSimpleName(); 16 | private static final Object LOCK = new Object(); 17 | private static final String DATABASE_NAME = "personlist"; 18 | private static AppDatabase sInstance; 19 | 20 | public static AppDatabase getInstance(Context context) { 21 | if (sInstance == null) { 22 | synchronized (LOCK) { 23 | Log.d(LOG_TAG, "Creating new database instance"); 24 | sInstance = Room.databaseBuilder(context.getApplicationContext(), 25 | AppDatabase.class, AppDatabase.DATABASE_NAME) 26 | .build(); 27 | } 28 | } 29 | Log.d(LOG_TAG, "Getting the database instance"); 30 | return sInstance; 31 | } 32 | 33 | public abstract AppDao personDao(); 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/database/AppExecutors.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.database; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import java.util.concurrent.Executor; 9 | import java.util.concurrent.Executors; 10 | 11 | public class AppExecutors { 12 | 13 | // For Singleton instantiation 14 | private static final Object LOCK = new Object(); 15 | private static AppExecutors sInstance; 16 | private final Executor diskIO; 17 | private final Executor mainThread; 18 | private final Executor networkIO; 19 | 20 | private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) { 21 | this.diskIO = diskIO; 22 | this.networkIO = networkIO; 23 | this.mainThread = mainThread; 24 | } 25 | 26 | public static AppExecutors getInstance() { 27 | if (sInstance == null) { 28 | synchronized (LOCK) { 29 | sInstance = new AppExecutors(Executors.newSingleThreadExecutor(), 30 | Executors.newFixedThreadPool(3), 31 | new MainThreadExecutor()); 32 | } 33 | } 34 | return sInstance; 35 | } 36 | 37 | public Executor diskIO() { 38 | return diskIO; 39 | } 40 | 41 | public Executor mainThread() { 42 | return mainThread; 43 | } 44 | 45 | public Executor networkIO() { 46 | return networkIO; 47 | } 48 | 49 | private static class MainThreadExecutor implements Executor { 50 | private Handler mainThreadHandler = new Handler(Looper.getMainLooper()); 51 | 52 | @Override 53 | public void execute(@NonNull Runnable command) { 54 | mainThreadHandler.post(command); 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/interfaces/OnDialogCallBack.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.interfaces; 2 | 3 | public interface OnDialogCallBack { 4 | void OnDialogCallBack(boolean isOk); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/interfaces/OnItemClickListener.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.interfaces; 2 | 3 | 4 | public interface OnItemClickListener { 5 | void OnClick(int position, int status); 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/model/CartItemModel.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.model; 2 | 3 | public class CartItemModel { 4 | private ProductModel productModel; 5 | private int numberOfProduct; 6 | private int color; 7 | 8 | public ProductModel getProductModel() { 9 | return productModel; 10 | } 11 | 12 | public void setProductModel(ProductModel productModel) { 13 | this.productModel = productModel; 14 | } 15 | 16 | public int getNumberOfProduct() { 17 | return numberOfProduct; 18 | } 19 | 20 | public void setNumberOfProduct(int numberOfProduct) { 21 | this.numberOfProduct = numberOfProduct; 22 | } 23 | 24 | public int getColor() { 25 | return color; 26 | } 27 | 28 | public void setColor(int color) { 29 | this.color = color; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/model/CheckoutInfo.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.model; 2 | 3 | import java.io.Serializable; 4 | 5 | public class CheckoutInfo implements Serializable { 6 | String name; 7 | String address1; 8 | String address2; 9 | String billingAddress; 10 | String city; 11 | String state; 12 | String zip; 13 | String country; 14 | String cardHolderName; 15 | String cardNumber; 16 | String expirationDate; 17 | String securityCode; 18 | boolean isSameShipping; 19 | 20 | public CheckoutInfo() { 21 | 22 | } 23 | 24 | public CheckoutInfo(String name, String address1, String address2, String billingAddress, String city, String state, String zip, String country, String cardHolderName, String cardNumber, String expirationDate, String securityCode) { 25 | this.name = name; 26 | this.address1 = address1; 27 | this.address2 = address2; 28 | this.billingAddress = billingAddress; 29 | this.city = city; 30 | this.state = state; 31 | this.zip = zip; 32 | this.country = country; 33 | this.cardHolderName = cardHolderName; 34 | this.cardNumber = cardNumber; 35 | this.expirationDate = expirationDate; 36 | this.securityCode = securityCode; 37 | } 38 | 39 | public String getBillingAddress() { 40 | return billingAddress; 41 | } 42 | 43 | public void setBillingAddress(String billingAddress) { 44 | this.billingAddress = billingAddress; 45 | } 46 | 47 | public String getCardHolderName() { 48 | return cardHolderName; 49 | } 50 | 51 | public void setCardHolderName(String cardHolderName) { 52 | this.cardHolderName = cardHolderName; 53 | } 54 | 55 | public String getCardNumber() { 56 | return cardNumber; 57 | } 58 | 59 | public void setCardNumber(String cardNumber) { 60 | this.cardNumber = cardNumber; 61 | } 62 | 63 | public String getExpirationDate() { 64 | return expirationDate; 65 | } 66 | 67 | public void setExpirationDate(String expirationDate) { 68 | this.expirationDate = expirationDate; 69 | } 70 | 71 | public String getSecurityCode() { 72 | return securityCode; 73 | } 74 | 75 | public void setSecurityCode(String securityCode) { 76 | this.securityCode = securityCode; 77 | } 78 | 79 | public String getName() { 80 | return name; 81 | } 82 | 83 | public void setName(String name) { 84 | this.name = name; 85 | } 86 | 87 | public String getAddress1() { 88 | return address1; 89 | } 90 | 91 | public void setAddress1(String address1) { 92 | this.address1 = address1; 93 | } 94 | 95 | public String getAddress2() { 96 | return address2; 97 | } 98 | 99 | public void setAddress2(String address2) { 100 | this.address2 = address2; 101 | } 102 | 103 | public String getCity() { 104 | return city; 105 | } 106 | 107 | public void setCity(String city) { 108 | this.city = city; 109 | } 110 | 111 | public String getState() { 112 | return state; 113 | } 114 | 115 | public void setState(String state) { 116 | this.state = state; 117 | } 118 | 119 | public String getZip() { 120 | return zip; 121 | } 122 | 123 | public void setZip(String zip) { 124 | this.zip = zip; 125 | } 126 | 127 | public String getCountry() { 128 | return country; 129 | } 130 | 131 | public void setCountry(String country) { 132 | this.country = country; 133 | } 134 | 135 | public boolean isSameShipping() { 136 | return isSameShipping; 137 | } 138 | 139 | public void setSameShipping(boolean sameShipping) { 140 | isSameShipping = sameShipping; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/model/ColorModel.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.model; 2 | 3 | public class ColorModel { 4 | private int colorImg; 5 | private int colorValue ; 6 | 7 | public ColorModel(int colorImg , int colorValue){ 8 | this.colorImg = colorImg; 9 | this.colorValue = colorValue; 10 | } 11 | 12 | public int getColorImg() { 13 | return colorImg; 14 | } 15 | 16 | public void setColorImg(int colorImg) { 17 | this.colorImg = colorImg; 18 | } 19 | 20 | public int getColorValue() { 21 | return colorValue; 22 | } 23 | 24 | public void setColorValue(int colorValue) { 25 | this.colorValue = colorValue; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/model/ColorModelConverters.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.model; 2 | 3 | import androidx.room.TypeConverter; 4 | 5 | import com.google.common.reflect.TypeToken; 6 | import com.google.gson.Gson; 7 | 8 | import java.lang.reflect.Type; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class ColorModelConverters { 13 | Gson gson = new Gson(); 14 | 15 | @TypeConverter 16 | public static List stringToSomeObjectList(String data) { 17 | if (data == null) { 18 | return Collections.emptyList(); 19 | } 20 | 21 | Type listType = new TypeToken>() {}.getType(); 22 | 23 | return new Gson().fromJson(data, listType); 24 | } 25 | 26 | @TypeConverter 27 | public static String someObjectListToString(List someObjects) { 28 | return new Gson().toJson(someObjects); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/model/MenuItem.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.model; 2 | 3 | public class MenuItem { 4 | public final String name; 5 | public final String contentDescription; 6 | 7 | public MenuItem(String name) { 8 | this.name = name; 9 | this.contentDescription = null; 10 | } 11 | 12 | public MenuItem(String name, String contentDescription) { 13 | this.name = name; 14 | this.contentDescription = contentDescription; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/model/ProductModel.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.model; 2 | 3 | import androidx.room.Entity; 4 | import androidx.room.PrimaryKey; 5 | import androidx.room.TypeConverters; 6 | 7 | import java.util.List; 8 | 9 | @Entity(tableName = "Product") 10 | public class ProductModel { 11 | @PrimaryKey(autoGenerate = true) 12 | int id; 13 | String title; 14 | double price; 15 | int rating; 16 | int colors; 17 | String desc; 18 | String currency; 19 | byte[] image; 20 | int imageVal; 21 | @TypeConverters(ColorModelConverters.class) 22 | List colorList; 23 | 24 | public int getId() { 25 | return id; 26 | } 27 | 28 | public void setId(int id) { 29 | this.id = id; 30 | } 31 | 32 | public String getTitle() { 33 | return title; 34 | } 35 | 36 | public void setTitle(String title) { 37 | this.title = title; 38 | } 39 | 40 | public double getPrice() { 41 | return price; 42 | } 43 | 44 | public void setPrice(double price) { 45 | this.price = price; 46 | } 47 | 48 | public int getRating() { 49 | return rating; 50 | } 51 | 52 | public void setRating(int rating) { 53 | this.rating = rating; 54 | } 55 | 56 | public int getColors() { 57 | return colors; 58 | } 59 | 60 | public void setColors(int colors) { 61 | this.colors = colors; 62 | } 63 | 64 | public String getDesc() { 65 | return desc; 66 | } 67 | 68 | public void setDesc(String desc) { 69 | this.desc = desc; 70 | } 71 | 72 | public byte[] getImage() { 73 | return image; 74 | } 75 | 76 | public void setImage(byte[] image) { 77 | this.image = image; 78 | } 79 | 80 | public String getCurrency() { 81 | return currency; 82 | } 83 | 84 | public void setCurrency(String currency) { 85 | this.currency = currency; 86 | } 87 | 88 | public int getImageVal() { 89 | return imageVal; 90 | } 91 | 92 | public void setImageVal(int imageVal) { 93 | this.imageVal = imageVal; 94 | } 95 | 96 | public List getColorList() { 97 | return colorList; 98 | } 99 | 100 | public void setColorList(List colorList) { 101 | this.colorList = colorList; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/model/User.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.model; 2 | 3 | import androidx.room.Entity; 4 | import androidx.room.Ignore; 5 | import androidx.room.PrimaryKey; 6 | 7 | @Entity(tableName = "person") 8 | public class User { 9 | @PrimaryKey(autoGenerate = true) 10 | int id; 11 | String name; 12 | String email; 13 | String number; 14 | String pincode; 15 | String city; 16 | 17 | @Ignore 18 | public User(String name, String email, String number, String pincode, String city) { 19 | this.name = name; 20 | this.email = email; 21 | this.number = number; 22 | this.pincode = pincode; 23 | this.city = city; 24 | } 25 | 26 | public User(int id, String name, String email, String number, String pincode, String city) { 27 | this.id = id; 28 | this.name = name; 29 | this.email = email; 30 | this.number = number; 31 | this.pincode = pincode; 32 | this.city = city; 33 | } 34 | 35 | public int getId() { 36 | return id; 37 | } 38 | 39 | public void setId(int id) { 40 | this.id = id; 41 | } 42 | 43 | public String getName() { 44 | return name; 45 | } 46 | 47 | public void setName(String name) { 48 | this.name = name; 49 | } 50 | 51 | public String getEmail() { 52 | return email; 53 | } 54 | 55 | public void setEmail(String email) { 56 | this.email = email; 57 | } 58 | 59 | public String getNumber() { 60 | return number; 61 | } 62 | 63 | public void setNumber(String number) { 64 | this.number = number; 65 | } 66 | 67 | public String getPincode() { 68 | return pincode; 69 | } 70 | 71 | public void setPincode(String pincode) { 72 | this.pincode = pincode; 73 | } 74 | 75 | public String getCity() { 76 | return city; 77 | } 78 | 79 | public void setCity(String city) { 80 | this.city = city; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils; 2 | 3 | public class Constants { 4 | 5 | public final int START_ACTIVITY = 0; 6 | public final int START_ACTIVITY_WITH_FINISH = 1; 7 | public final int START_ACTIVITY_WITH_CLEAR_BACK_STACK = 2; 8 | public final int START_ACTIVITY_WITH_TOP = 3; 9 | public final int FINISH_CURRENT_ACTIVITY = 4; 10 | 11 | 12 | public static final String REQUEST_FRAGMENT = "REQUEST_FRAGMENT"; 13 | public static final String SELECTED_TAB = "SELECTED_TAB"; 14 | public static final String ARG_PARAM1 = "ARG_PARAM1"; 15 | public static final String ARG_PARAM2 = "ARG_PARAM2"; 16 | public static final String ARG_PARAM3 = "ARG_PARAM3"; 17 | public static final String ARG_LIST = "ARG_LIST"; 18 | 19 | public static boolean is_biometric = false; 20 | 21 | public int CURRENT_FRAGMENT = -1; 22 | public int PREVIOUS_FRAGMENT = -1; 23 | public int PREVIOUS_TAB = -1; 24 | 25 | //Preferences 26 | public final String APP_SHARED_PREFERENCE = "SAMIRA_SHARED_PREFERENCE"; 27 | 28 | public static final int BLACK = 1; 29 | public static final int BLUE = 2; 30 | public static final int GRAY = 3; 31 | public static final int GREEN = 4; 32 | public static final int RED = 5; 33 | public static final int YELLOW = 6; 34 | public static final int PINK = 7; 35 | public static final int VIOLET = 8; 36 | public static final int BROWN = 9; 37 | public static final int ORANGE = 10; 38 | 39 | public final String LOGOUT = "LOGOUT"; 40 | public final String CHECKOUT = "CHECKOUT"; 41 | public final String CURRENCY = "$"; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/DatabaseRepository.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils; 2 | 3 | import android.app.Application; 4 | import android.os.AsyncTask; 5 | 6 | import androidx.lifecycle.LiveData; 7 | 8 | import com.saucelabs.mydemoapp.android.database.AppDao; 9 | import com.saucelabs.mydemoapp.android.database.AppDatabase; 10 | import com.saucelabs.mydemoapp.android.model.ProductModel; 11 | 12 | import java.util.List; 13 | 14 | public class DatabaseRepository { 15 | private AppDao noteDao; 16 | private LiveData> allNotes; 17 | private LiveData dataInserted; 18 | 19 | public DatabaseRepository(Application application) { //application is subclass of context 20 | AppDatabase database = AppDatabase.getInstance(application); 21 | noteDao = database.personDao(); 22 | 23 | } 24 | 25 | public void insert(ProductModel note) { 26 | new InsertNoteAsyncTask(noteDao).execute(note); 27 | } 28 | 29 | 30 | public void insert(List note) { 31 | new InsertProductListAsyncTask(noteDao,note).execute(); 32 | } 33 | 34 | public void update(ProductModel note) { 35 | new UpdateNoteAsyncTask(noteDao).execute(note); 36 | } 37 | 38 | public void delete(ProductModel note) { 39 | new DeleteNoteAsyncTask(noteDao).execute(note); 40 | } 41 | 42 | public void deleteAllNotes() { 43 | new DeleteAllNotesAsyncTask(noteDao).execute(); 44 | } 45 | 46 | public LiveData> getAllNotes() { 47 | return allNotes; 48 | } 49 | 50 | private static class InsertProductListAsyncTask extends AsyncTask { //static : doesnt have reference to the 51 | // repo itself otherwise it could cause memory leak! 52 | private AppDao noteDao; 53 | private List list; 54 | private InsertProductListAsyncTask(AppDao noteDao,List list) { 55 | this.noteDao = noteDao; 56 | this.list = list; 57 | } 58 | @Override 59 | protected Void doInBackground(ProductModel... notes) { // ... is similar to array 60 | noteDao.insertProduct(list); //single note 61 | return null; 62 | } 63 | } 64 | 65 | private static class InsertNoteAsyncTask extends AsyncTask { //static : doesnt have reference to the 66 | // repo itself otherwise it could cause memory leak! 67 | private AppDao noteDao; 68 | private InsertNoteAsyncTask(AppDao noteDao) { 69 | this.noteDao = noteDao; 70 | } 71 | @Override 72 | protected Void doInBackground(ProductModel... notes) { // ... is similar to array 73 | noteDao.insertProduct(notes[0]); //single note 74 | return null; 75 | } 76 | } 77 | 78 | private static class UpdateNoteAsyncTask extends AsyncTask { 79 | private AppDao noteDao; 80 | private UpdateNoteAsyncTask(AppDao noteDao) { //constructor as the class is static 81 | this.noteDao = noteDao; 82 | } 83 | @Override 84 | protected Void doInBackground(ProductModel... notes) { 85 | // noteDao.Update(notes[0]); 86 | return null; 87 | } 88 | } 89 | 90 | private static class DeleteNoteAsyncTask extends AsyncTask { 91 | private AppDao noteDao; 92 | private DeleteNoteAsyncTask(AppDao noteDao) { 93 | this.noteDao = noteDao; 94 | } 95 | @Override 96 | protected Void doInBackground(ProductModel... notes) { 97 | // noteDao.Delete(notes[0]); 98 | return null; 99 | } 100 | } 101 | 102 | private static class DeleteAllNotesAsyncTask extends AsyncTask { 103 | private AppDao noteDao; 104 | private DeleteAllNotesAsyncTask(AppDao noteDao) { 105 | this.noteDao = noteDao; 106 | } 107 | @Override 108 | protected Void doInBackground(Void... voids) { 109 | // noteDao.DeleteAllNotes(); 110 | return null; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/GridSpacingItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils; 2 | 3 | import android.graphics.Rect; 4 | import android.view.View; 5 | 6 | import androidx.recyclerview.widget.RecyclerView; 7 | 8 | public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { 9 | 10 | private int spanCount; 11 | private int spacing; 12 | private boolean includeEdge; 13 | 14 | public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) { 15 | this.spanCount = spanCount; 16 | this.spacing = spacing; 17 | this.includeEdge = includeEdge; 18 | } 19 | 20 | @Override 21 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 22 | int position = parent.getChildAdapterPosition(view); // item position 23 | int column = position % spanCount; // item column 24 | 25 | if (includeEdge) { 26 | outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) 27 | outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) 28 | 29 | if (position < spanCount) { // top edge 30 | outRect.top = spacing; 31 | } 32 | outRect.bottom = spacing; // item bottom 33 | } else { 34 | outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) 35 | outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) 36 | if (position >= spanCount) { 37 | outRect.top = spacing; // item top 38 | } 39 | } 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/Network.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.IOException; 6 | import java.net.URI; 7 | import java.net.URISyntaxException; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.UUID; 12 | 13 | import okhttp3.Headers; 14 | import okhttp3.MediaType; 15 | import okhttp3.OkHttpClient; 16 | import okhttp3.Request; 17 | import okhttp3.RequestBody; 18 | import okhttp3.Response; 19 | import okhttp3.ResponseBody; 20 | import okio.Buffer; 21 | 22 | public class Network { 23 | 24 | private static Headers toHeaders(List headers) { 25 | Headers.Builder builder = new Headers.Builder(); 26 | 27 | for (String line: headers) { 28 | builder.add(line); 29 | } 30 | 31 | return builder.build(); 32 | } 33 | 34 | private static Request buildRequest(String method, String url, List headers, String requestBody, String contentType) { 35 | Request.Builder builder = new Request.Builder() 36 | .headers(toHeaders(headers)) 37 | .url(url); 38 | 39 | if (requestBody != null) { 40 | RequestBody body = RequestBody.create(requestBody, MediaType.parse(contentType)); 41 | builder.method(method, body); 42 | } else { 43 | builder.method(method, null); 44 | } 45 | 46 | return builder.build(); 47 | } 48 | 49 | private static Response sendRequest(OkHttpClient client, Request request) throws IOException { 50 | return client.newCall(request).execute(); 51 | } 52 | 53 | private static byte[] getRequestBodyBytes(Request request) throws IOException { 54 | Buffer requestBuffer = new Buffer(); 55 | RequestBody requestBody = request.body(); 56 | if (requestBody != null) { 57 | requestBody.writeTo(requestBuffer); 58 | } 59 | 60 | return requestBuffer.readByteArray(); 61 | } 62 | 63 | private static byte[] getResponseBodyBytes(Response response) throws IOException { 64 | ResponseBody responseBody = response.body(); 65 | return responseBody != null ? responseBody.bytes() : new byte[0]; 66 | } 67 | 68 | private static String getRequestHeaders(Request request) { 69 | StringBuilder requestHeaders = new StringBuilder(); 70 | Map> requestHeadersMap = request.headers().toMultimap(); 71 | for (Map.Entry> entry : requestHeadersMap.entrySet()) { 72 | for (String val : entry.getValue()) { 73 | requestHeaders.append(entry.getKey()).append(": ").append(val).append("\n"); 74 | } 75 | } 76 | 77 | return requestHeaders.toString().trim(); 78 | } 79 | 80 | private static String getResponseHeaders(Response response) { 81 | StringBuilder responseHeaders = new StringBuilder(); 82 | Map> responseHeadersMap = response.headers().toMultimap(); 83 | for (Map.Entry> entry : responseHeadersMap.entrySet()) { 84 | for (String val : entry.getValue()) { 85 | responseHeaders.append(entry.getKey()).append(": ").append(val).append("\n"); 86 | } 87 | } 88 | 89 | return responseHeaders.toString().trim(); 90 | } 91 | 92 | public static void fetch(String method, String url, String body, String contentType) { 93 | new Thread(() -> { 94 | try { 95 | List headers = new ArrayList<>(); 96 | headers.add("User-Agent: my-demo-app-android"); 97 | headers.add("X-Trace-ID: " + UUID.randomUUID().toString()); 98 | 99 | OkHttpClient client = new OkHttpClient(); 100 | Request request = buildRequest(method, url, headers, body, contentType); 101 | Log.v("Network", "Sending " + method + " request to " + url); 102 | 103 | long startTime = System.currentTimeMillis(); 104 | Response response = sendRequest(client, request); 105 | long endTime = System.currentTimeMillis(); 106 | 107 | byte[] requestBodyBytes = getRequestBodyBytes(request); 108 | byte[] responseBodyBytes = getResponseBodyBytes(response); 109 | 110 | String requestHeaders = getRequestHeaders(request); 111 | String responseHeaders = getResponseHeaders(response); 112 | } catch (IOException ignored) { 113 | } 114 | }).start(); 115 | } 116 | 117 | public static void fetch(String url) { 118 | fetch("GET", url, null, null); 119 | } 120 | 121 | public static void post(String url, String body, String contentType) { 122 | fetch("POST", url, body, contentType); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/QrCodeAnalyzer.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils; 2 | 3 | import android.graphics.ImageFormat; 4 | import android.os.Build; 5 | import android.util.Log; 6 | 7 | import androidx.camera.core.ImageAnalysis; 8 | import androidx.camera.core.ImageProxy; 9 | 10 | import com.google.zxing.BinaryBitmap; 11 | import com.google.zxing.DecodeHintType; 12 | import com.google.zxing.MultiFormatReader; 13 | import com.google.zxing.NotFoundException; 14 | import com.google.zxing.PlanarYUVLuminanceSource; 15 | import com.google.zxing.Result; 16 | import com.google.zxing.common.HybridBinarizer; 17 | 18 | import java.nio.ByteBuffer; 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | public class QrCodeAnalyzer implements ImageAnalysis.Analyzer { 26 | 27 | private final List yuvFormats; 28 | private final MultiFormatReader reader; 29 | private final OnQrCodesDetectedListener onQrCodesDetectedListener; 30 | 31 | public interface OnQrCodesDetectedListener { 32 | void onQrCodesDetected(Result qrCode); 33 | } 34 | 35 | public QrCodeAnalyzer(OnQrCodesDetectedListener listener) { 36 | this.onQrCodesDetectedListener = listener; 37 | 38 | yuvFormats = new ArrayList<>(); 39 | yuvFormats.add(ImageFormat.YUV_420_888); 40 | 41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 42 | yuvFormats.addAll(Arrays.asList(ImageFormat.YUV_422_888, ImageFormat.YUV_444_888)); 43 | } 44 | 45 | reader = new MultiFormatReader(); 46 | Map map = new HashMap<>(); 47 | map.put(DecodeHintType.POSSIBLE_FORMATS, new ArrayList<>(Arrays.asList(com.google.zxing.BarcodeFormat.QR_CODE))); 48 | reader.setHints(map); 49 | } 50 | 51 | @Override 52 | public void analyze(ImageProxy image) { 53 | if (!yuvFormats.contains(image.getFormat())) { 54 | Log.e("QRCodeAnalyzer", "Expected YUV, now = " + image.getFormat()); 55 | image.close(); 56 | return; 57 | } else { 58 | Log.d("CameraXApp-Eyal", "I am here"); 59 | } 60 | 61 | ByteBuffer buffer = image.getPlanes()[0].getBuffer(); 62 | byte[] data = toByteArray(buffer); 63 | 64 | PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource( 65 | data, 66 | image.getWidth(), 67 | image.getHeight(), 68 | 0, 69 | 0, 70 | image.getWidth(), 71 | image.getHeight(), 72 | false 73 | ); 74 | 75 | BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source)); 76 | try { 77 | Result result = reader.decode(binaryBitmap); 78 | onQrCodesDetectedListener.onQrCodesDetected(result); 79 | } catch (NotFoundException e) { 80 | e.printStackTrace(); 81 | } 82 | image.close(); 83 | } 84 | 85 | private byte[] toByteArray(ByteBuffer buffer) { 86 | buffer.rewind(); 87 | byte[] data = new byte[buffer.remaining()]; 88 | buffer.get(data); 89 | return data; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/SingletonClass.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils; 2 | 3 | import android.content.Context; 4 | 5 | import com.saucelabs.mydemoapp.android.model.CheckoutInfo; 6 | import com.saucelabs.mydemoapp.android.model.CartItemModel; 7 | 8 | import com.google.gson.Gson; 9 | 10 | import java.io.File; 11 | import java.io.FileOutputStream; 12 | import java.io.IOException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class SingletonClass extends Methods { 17 | private static SingletonClass sSoleInstance; 18 | 19 | private Gson gson; 20 | public CheckoutInfo checkoutInfo = new CheckoutInfo(); 21 | public CheckoutInfo billingInfo = new CheckoutInfo(); 22 | public List cartItemList; 23 | public boolean isLogin = false; 24 | public boolean hasVisualChanges = false; 25 | 26 | private SingletonClass() { 27 | cartItemList = new ArrayList<>(); 28 | } 29 | 30 | public static SingletonClass getInstance() { 31 | if (sSoleInstance == null) { 32 | sSoleInstance = new SingletonClass(); 33 | } 34 | 35 | return sSoleInstance; 36 | } 37 | 38 | public Gson gson() { 39 | if (gson == null) 40 | gson = new Gson(); 41 | return gson; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/base/BaseAdapter.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils.base; 2 | 3 | import android.app.Activity; 4 | import android.view.ViewGroup; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.recyclerview.widget.RecyclerView; 8 | 9 | import com.saucelabs.mydemoapp.android.interfaces.OnItemClickListener; 10 | import com.saucelabs.mydemoapp.android.utils.SingletonClass; 11 | 12 | import java.util.List; 13 | 14 | 15 | 16 | 17 | public class BaseAdapter extends RecyclerView.Adapter { 18 | public Activity mAct; 19 | public SingletonClass ST; 20 | public OnItemClickListener listener; 21 | public List list; 22 | 23 | protected BaseAdapter(Activity mAct, List list, OnItemClickListener listener) { 24 | this.mAct = mAct; 25 | this.ST = SingletonClass.getInstance(); 26 | this.listener = listener; 27 | this.list = list; 28 | } 29 | 30 | 31 | @Override 32 | public RecyclerView.ViewHolder onCreateViewHolder( ViewGroup parent, int viewType) { 33 | return null; 34 | } 35 | 36 | @Override 37 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 38 | 39 | } 40 | 41 | @Override 42 | public int getItemCount() { 43 | return 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/base/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils.base; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | 6 | import androidx.fragment.app.Fragment; 7 | 8 | import com.saucelabs.mydemoapp.android.MyApplication; 9 | import com.saucelabs.mydemoapp.android.database.AppDatabase; 10 | import com.saucelabs.mydemoapp.android.utils.SingletonClass; 11 | 12 | 13 | public class BaseFragment extends Fragment { 14 | public Activity mAct; 15 | public MyApplication app; 16 | public SingletonClass ST; 17 | public AppDatabase mDb; 18 | 19 | public String mParam1; 20 | public String mParam2; 21 | public int mParam3; 22 | 23 | @Override 24 | public void onAttach(Context context) { 25 | super.onAttach(context); 26 | app = (MyApplication) getActivity().getApplication(); 27 | ST = SingletonClass.getInstance(); 28 | } 29 | 30 | @Override 31 | public void onResume() { 32 | super.onResume(); 33 | } 34 | 35 | public boolean OnBackPress() { 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/base/BaseModel.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils.base; 2 | 3 | import java.io.Serializable; 4 | 5 | public abstract class BaseModel implements Serializable { 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/base/BaseViewModel.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils.base; 2 | 3 | import androidx.lifecycle.ViewModel; 4 | 5 | public class BaseViewModel extends ViewModel { 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/utils/preference/PrefManager.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.utils.preference; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | 7 | import com.saucelabs.mydemoapp.android.utils.Methods; 8 | import com.saucelabs.mydemoapp.android.utils.base.BaseModel; 9 | import com.google.gson.Gson; 10 | 11 | import java.lang.reflect.Type; 12 | 13 | public class PrefManager extends Methods { 14 | private SharedPreferences getPrefs(Activity mAct) { 15 | return mAct.getSharedPreferences(APP_SHARED_PREFERENCE, Context.MODE_PRIVATE); 16 | } 17 | 18 | public int getMyIntPref(Activity mAct, String prefName, int defVal) { 19 | return getPrefs(mAct).getInt(prefName, defVal); 20 | } 21 | 22 | public void setMyIntPref(Activity mAct, String prefName, int value) { 23 | getPrefs(mAct).edit().putInt(prefName, value).apply(); 24 | } 25 | 26 | public String getMyStringPref(Activity mAct, String prefName, String defVal) { 27 | return getPrefs(mAct).getString(prefName, defVal); 28 | } 29 | 30 | public void setMyStringPref(Activity mAct, String prefName, String value) { 31 | getPrefs(mAct).edit().putString(prefName, value).apply(); 32 | } 33 | 34 | public BaseModel getMyObjectPref(Activity mAct, String prefName, String defVal, Class aClass) { 35 | Gson gson = new Gson(); 36 | String json = getPrefs(mAct).getString(prefName, defVal); 37 | if (json != null) { 38 | BaseModel baseModel = gson.fromJson(json, (Type) aClass); 39 | return baseModel; 40 | } 41 | 42 | return null; 43 | } 44 | 45 | public void setMyObjectPref(Activity mAct, String prefName, BaseModel model) { 46 | Gson gson = new Gson(); 47 | String json = gson.toJson(model); 48 | 49 | getPrefs(mAct).edit().putString(prefName, json).apply(); 50 | } 51 | 52 | public boolean getMyBooleanPref(Activity mAct, String prefName, boolean defVal) { 53 | return getPrefs(mAct).getBoolean(prefName, defVal); 54 | } 55 | 56 | public void setMyBooleanPref(Activity mAct, String prefName, boolean value) { 57 | getPrefs(mAct).edit().putBoolean(prefName, value).apply(); 58 | } 59 | 60 | public long getMyLongPref(Activity mAct, String prefName, long defVal) { 61 | return getPrefs(mAct).getLong(prefName, defVal); 62 | } 63 | 64 | public void setMyLongPref(Activity mAct, String prefName, long value) { 65 | getPrefs(mAct).edit().putLong(prefName, value).apply(); 66 | } 67 | 68 | public void setRatePref(Activity mAct, String prefName, boolean val) { 69 | getPrefs(mAct).edit().putBoolean(prefName, val).apply(); 70 | } 71 | 72 | public boolean getRatePref(Activity mAct, String prefName, boolean val) { 73 | return getPrefs(mAct).getBoolean(prefName, val); 74 | } 75 | 76 | public boolean getRateTimePref(Activity mAct, String prefName, boolean value) { 77 | return getPrefs(mAct).getBoolean(prefName, value); 78 | } 79 | 80 | public void setRateTimePref(Activity mAct, String prefName, boolean value) { 81 | getPrefs(mAct).edit().putBoolean(prefName, value).apply(); 82 | } 83 | 84 | public void removePref(Activity mAct, String prefName) { 85 | getPrefs(mAct).edit().remove(prefName).apply(); 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/activities/DebugCrashActivity.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.activities; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.Toast; 6 | 7 | import androidx.databinding.DataBindingUtil; 8 | 9 | import com.saucelabs.mydemoapp.android.MyApplication; 10 | import com.saucelabs.mydemoapp.android.R; 11 | import com.saucelabs.mydemoapp.android.databinding.ActivityDebugCrashBinding; 12 | import com.saucelabs.mydemoapp.android.databinding.ActivityDebugFeedbackBinding; 13 | import com.saucelabs.mydemoapp.android.utils.base.BaseActivity; 14 | 15 | import java.util.ArrayList; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.TreeMap; 20 | 21 | import backtraceio.library.BacktraceClient; 22 | 23 | public class DebugCrashActivity extends BaseActivity { 24 | private ActivityDebugCrashBinding binding; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | binding = DataBindingUtil.setContentView(this, R.layout.activity_debug_crash); 30 | initialize(); 31 | } 32 | 33 | private void initialize() { 34 | 35 | binding.causeUncaughtExceptionButton.setOnClickListener(new View.OnClickListener() { 36 | @Override 37 | public void onClick(View v) { 38 | if (MyApplication.backtraceClient != null) { 39 | throw new RuntimeException("You clicked on the wrong button!"); 40 | } 41 | } 42 | }); 43 | 44 | binding.causeNativeCrashButton.setOnClickListener(new View.OnClickListener() { 45 | @Override 46 | public void onClick(View v) { 47 | if (MyApplication.backtraceClient == null) { 48 | Toast.makeText(DebugCrashActivity.this, "Backtrace client not initialized, cannot crash app", Toast.LENGTH_LONG).show(); 49 | return; 50 | } 51 | 52 | MyApplication.backtraceClient.nativeCrash(); 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/activities/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.activities; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import androidx.databinding.DataBindingUtil; 7 | import androidx.lifecycle.Observer; 8 | import androidx.lifecycle.ViewModelProviders; 9 | 10 | import com.saucelabs.mydemoapp.android.R; 11 | import com.saucelabs.mydemoapp.android.database.AppDao; 12 | import com.saucelabs.mydemoapp.android.database.AppDatabase; 13 | import com.saucelabs.mydemoapp.android.database.AppExecutors; 14 | import com.saucelabs.mydemoapp.android.databinding.ActivitySplashBinding; 15 | import com.saucelabs.mydemoapp.android.model.ProductModel; 16 | import com.saucelabs.mydemoapp.android.utils.base.BaseActivity; 17 | import com.saucelabs.mydemoapp.android.viewModel.SplashViewModel; 18 | import com.saucelabs.mydemoapp.android.viewModel.SplashViewModelFactory; 19 | 20 | import java.util.List; 21 | 22 | public class SplashActivity extends BaseActivity { 23 | private ActivitySplashBinding binding; 24 | SplashViewModel viewModel; 25 | private AppDatabase mDb; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | binding = DataBindingUtil.setContentView(this, R.layout.activity_splash); 31 | 32 | viewModel = ViewModelProviders.of(this, new SplashViewModelFactory(this.getApplication())).get(SplashViewModel.class); 33 | initViews(); 34 | } 35 | 36 | private void initViews() { 37 | mDb = AppDatabase.getInstance(getApplicationContext()); 38 | checkObserver(); 39 | } 40 | 41 | private void checkObserver() { 42 | viewModel.allProducts.observe(this, new Observer>() { 43 | @Override 44 | public void onChanged(List productModels) { 45 | if (productModels != null && productModels.size() > 0) { 46 | ST.startActivity(mAct, MainActivity.class, ST.START_ACTIVITY_WITH_FINISH); 47 | } else { 48 | populateProductsDb(viewModel); 49 | } 50 | } 51 | }); 52 | 53 | viewModel.pb.observe(this, new Observer() { 54 | @Override 55 | public void onChanged(Integer integer) { 56 | if (integer == View.GONE) { 57 | ST.startActivity(mAct, MainActivity.class, ST.START_ACTIVITY_WITH_FINISH); 58 | } 59 | } 60 | }); 61 | } 62 | 63 | @Override 64 | protected void onResume() { 65 | super.onResume(); 66 | } 67 | 68 | public void insertProducts(AppDao dao, List list) { 69 | AppExecutors.getInstance().diskIO().execute(new Runnable() { 70 | @Override 71 | public void run() { 72 | dao.insertProduct(list); 73 | ST.startActivity(mAct, MainActivity.class, ST.START_ACTIVITY_WITH_FINISH); 74 | } 75 | }); 76 | } 77 | 78 | private void checkLocalDataBase() { 79 | AppExecutors.getInstance().diskIO().execute(new Runnable() { 80 | @Override 81 | public void run() { 82 | final List productList = mDb.personDao().getAllProducts(); 83 | if (productList != null && productList.size() == 0) { 84 | populateProductsDb(viewModel); 85 | } else { 86 | // myVieswModel.addDelays(); 87 | ST.startActivity(mAct, MainActivity.class, ST.START_ACTIVITY_WITH_FINISH); 88 | } 89 | } 90 | }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/activities/VirtualUsbActivity.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.activities; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.widget.TextView; 7 | 8 | import com.saucelabs.mydemoapp.android.Config; 9 | import com.saucelabs.mydemoapp.android.R; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.net.ServerSocket; 15 | import java.net.Socket; 16 | import java.net.SocketTimeoutException; 17 | 18 | public class VirtualUsbActivity extends Activity { 19 | 20 | private static final int SERVER_SOCKET_PORT = 50000; 21 | 22 | volatile boolean serverSocketActive; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | 28 | setContentView(R.layout.activity_virtual_usb); 29 | } 30 | 31 | @Override 32 | protected void onResume() { 33 | super.onResume(); 34 | 35 | Thread t = new Thread(this::startServer); 36 | t.setName("VirtualUSB_ServerSocket_Thread"); 37 | t.start(); 38 | } 39 | 40 | @Override 41 | protected void onPause() { 42 | stopServer(); 43 | super.onPause(); 44 | } 45 | 46 | private void stopServer() { 47 | serverSocketActive = false; 48 | } 49 | 50 | private void onNewClientMessage(String message) { 51 | TextView textView = this.findViewById(R.id.virtual_usb_message); 52 | textView.setText(message); 53 | } 54 | 55 | private void acceptConnection(ServerSocket serverSocket) throws IOException { 56 | 57 | // we might not be getting a socket because accept() is set up to timeout after 1 second 58 | Socket clientSocket = serverSocket.accept(); 59 | 60 | Thread t = new Thread(() -> { 61 | try { 62 | // read only the first line from socket 63 | BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 64 | String line = in.readLine(); 65 | onNewClientMessage(line); 66 | 67 | // Close the client socket and input stream 68 | in.close(); 69 | clientSocket.close(); 70 | } catch (IOException e) { 71 | Log.e(Config.TAG, "Exception while reading from socket", e); 72 | } 73 | }); 74 | 75 | t.start(); 76 | } 77 | 78 | private void startServer() { 79 | 80 | serverSocketActive = true; 81 | 82 | try (ServerSocket serverSocket = new ServerSocket(SERVER_SOCKET_PORT)) { 83 | serverSocket.setSoTimeout(1000); 84 | 85 | while (serverSocketActive) { 86 | try { 87 | acceptConnection(serverSocket); 88 | } catch (SocketTimeoutException ste) { 89 | // ignored 90 | } 91 | } 92 | } catch (IOException e) { 93 | Log.e(Config.TAG, "Exception during virtual-usb demo", e); 94 | } 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/adapters/ColorsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.adapters; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.databinding.DataBindingUtil; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | import com.saucelabs.mydemoapp.android.R; 14 | import com.saucelabs.mydemoapp.android.databinding.ItemColorBinding; 15 | import com.saucelabs.mydemoapp.android.interfaces.OnItemClickListener; 16 | import com.saucelabs.mydemoapp.android.model.ColorModel; 17 | import com.saucelabs.mydemoapp.android.utils.Constants; 18 | import com.saucelabs.mydemoapp.android.utils.SingletonClass; 19 | 20 | import java.util.List; 21 | 22 | public class ColorsAdapter extends RecyclerView.Adapter { 23 | Activity mAct; 24 | List list; 25 | OnItemClickListener clickListener; 26 | SingletonClass ST; 27 | 28 | int selectedPos = 0; 29 | 30 | public ColorsAdapter(Activity mAct, List list, OnItemClickListener clickListener) { 31 | this.mAct = mAct; 32 | this.list = list; 33 | ST = SingletonClass.getInstance(); 34 | this.clickListener = clickListener; 35 | } 36 | 37 | @NonNull 38 | @Override 39 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 40 | ItemColorBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), 41 | R.layout.item_color, parent, false); 42 | return new ViewHolder(binding); 43 | } 44 | 45 | @Override 46 | public void onBindViewHolder(@NonNull ViewHolder holder, @SuppressLint("RecyclerView") int position) { 47 | ColorModel model = list.get(position); 48 | holder.binding.colorIV.setImageResource(model.getColorImg()); 49 | holder.binding.colorIV.setContentDescription(holder.getContentDescription(model)); 50 | 51 | if (selectedPos == position) { 52 | holder.binding.aroundIV.setImageResource(holder.getAroundIV(model)); 53 | holder.binding.aroundIV.setVisibility(View.VISIBLE); 54 | } else { 55 | holder.binding.aroundIV.setVisibility(View.INVISIBLE); 56 | } 57 | 58 | holder.binding.getRoot().setOnClickListener(new View.OnClickListener() { 59 | @Override 60 | public void onClick(View view) { 61 | selectedPos = position; 62 | notifyDataSetChanged(); 63 | clickListener.OnClick(position, -1); 64 | } 65 | }); 66 | } 67 | 68 | @Override 69 | public int getItemCount() { 70 | return list.size(); 71 | } 72 | 73 | public static class ViewHolder extends RecyclerView.ViewHolder { 74 | ItemColorBinding binding; 75 | 76 | public ViewHolder(@NonNull ItemColorBinding itemView) { 77 | super(itemView.getRoot()); 78 | this.binding = itemView; 79 | } 80 | 81 | private String getContentDescription(ColorModel model) { 82 | switch (model.getColorValue()) { 83 | 84 | case Constants.BLACK: 85 | return "Black color"; 86 | 87 | case Constants.GREEN: 88 | return "Green color"; 89 | 90 | case Constants.GRAY: 91 | return "Gray color"; 92 | 93 | case Constants.BLUE: 94 | return "Blue color"; 95 | 96 | default: 97 | return "Unknown color"; 98 | } 99 | } 100 | 101 | private int getAroundIV(ColorModel model) { 102 | switch (model.getColorValue()) { 103 | case Constants.BLACK: 104 | return R.drawable.ic_black_circle_side; 105 | 106 | case Constants.GREEN: 107 | return R.drawable.ic_green_circle_side; 108 | 109 | case Constants.GRAY: 110 | return R.drawable.ic_gray_circle_side; 111 | 112 | case Constants.BLUE: 113 | return R.drawable.ic_blue_circle_side; 114 | 115 | default: 116 | return 0; 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/adapters/MenuAdapter.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.adapters; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.databinding.DataBindingUtil; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | import com.saucelabs.mydemoapp.android.R; 14 | import com.saucelabs.mydemoapp.android.databinding.MenuItemBinding; 15 | import com.saucelabs.mydemoapp.android.interfaces.OnItemClickListener; 16 | import com.saucelabs.mydemoapp.android.model.MenuItem; 17 | 18 | import java.util.List; 19 | 20 | public class MenuAdapter extends RecyclerView.Adapter { 21 | private List list; 22 | private Context context; 23 | private OnItemClickListener listener; 24 | 25 | public MenuAdapter(List list, Context context, OnItemClickListener listener) { 26 | this.list = list; 27 | this.context = context; 28 | this.listener = listener; 29 | } 30 | 31 | @NonNull 32 | @Override 33 | public MenuAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 34 | MenuItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), 35 | R.layout.menu_item, parent, false); 36 | // View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.row_grid_recent_transfer2, viewGroup, false); 37 | return new ViewHolder(binding); 38 | } 39 | 40 | @Override 41 | public void onBindViewHolder(@NonNull MenuAdapter.ViewHolder holder, @SuppressLint("RecyclerView") int position) { 42 | 43 | if(position == 0){ 44 | holder.binding.topViewTV.setVisibility(View.VISIBLE); 45 | }else{ 46 | holder.binding.topViewTV.setVisibility(View.GONE); 47 | } 48 | 49 | if(position == 0 || position == 4){ 50 | holder.binding.itemV.setVisibility(View.VISIBLE); 51 | }else{ 52 | holder.binding.itemV.setVisibility(View.INVISIBLE); 53 | } 54 | 55 | holder.binding.itemTV.setText(list.get(position).name); 56 | String contentDescr = list.get(position).contentDescription; 57 | if (contentDescr != null) { 58 | holder.binding.itemTV.setContentDescription(contentDescr); 59 | holder.binding.itemTV.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 60 | } 61 | 62 | holder.binding.itemTV.setOnClickListener(new View.OnClickListener() { 63 | @Override 64 | public void onClick(View view) { 65 | listener.OnClick(position,-1); 66 | } 67 | }); 68 | 69 | } 70 | 71 | @Override 72 | public int getItemCount() { 73 | return list.size(); 74 | } 75 | 76 | public class ViewHolder extends RecyclerView.ViewHolder { 77 | MenuItemBinding binding; 78 | 79 | public ViewHolder(@NonNull MenuItemBinding binding) { 80 | super(binding.getRoot()); 81 | this.binding = binding; 82 | } 83 | } 84 | 85 | @Override 86 | public long getItemId(int position) { 87 | return position; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/adapters/ProductsAdapter.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.adapters; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Activity; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.databinding.DataBindingUtil; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | import com.saucelabs.mydemoapp.android.R; 14 | import com.saucelabs.mydemoapp.android.databinding.ItemProductsBinding; 15 | import com.saucelabs.mydemoapp.android.interfaces.OnItemClickListener; 16 | import com.saucelabs.mydemoapp.android.model.ProductModel; 17 | import com.saucelabs.mydemoapp.android.utils.SingletonClass; 18 | 19 | import java.util.List; 20 | 21 | public class ProductsAdapter extends RecyclerView.Adapter { 22 | Activity mAct; 23 | List list; 24 | OnItemClickListener clickListener; 25 | SingletonClass ST; 26 | 27 | public ProductsAdapter(Activity mAct, List list, OnItemClickListener clickListener) { 28 | this.mAct = mAct; 29 | this.list = list; 30 | ST = SingletonClass.getInstance(); 31 | this.clickListener = clickListener; 32 | } 33 | 34 | @NonNull 35 | @Override 36 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 37 | ItemProductsBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), 38 | R.layout.item_products, parent, false); 39 | return new ViewHolder(binding); 40 | } 41 | 42 | @Override 43 | public void onBindViewHolder(@NonNull ViewHolder holder, @SuppressLint("RecyclerView") int position) { 44 | ProductModel model = list.get(position); 45 | 46 | holder.binding.titleTV.setText(model.getTitle()); 47 | holder.binding.titleTV.setContentDescription("Product Title"); 48 | holder.binding.priceTV.setText(model.getCurrency() + " " + model.getPrice()); 49 | holder.binding.priceTV.setContentDescription("Product Price"); 50 | holder.handleRating(model.getRating()); 51 | holder.binding.productIV.setContentDescription("Product Image"); 52 | holder.binding.productIV.setImageResource(model.getImageVal()); 53 | 54 | holder.binding.productIV.setOnClickListener(new View.OnClickListener() { 55 | @Override 56 | public void onClick(View view) { 57 | clickListener.OnClick(position, 1); 58 | } 59 | }); 60 | } 61 | 62 | @Override 63 | public int getItemCount() { 64 | return list.size(); 65 | } 66 | 67 | public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 68 | ItemProductsBinding binding; 69 | 70 | public ViewHolder(@NonNull ItemProductsBinding itemView) { 71 | super(itemView.getRoot()); 72 | this.binding = itemView; 73 | 74 | binding.rattingV.start1IV.setOnClickListener(this); 75 | binding.rattingV.start2IV.setOnClickListener(this); 76 | binding.rattingV.start3IV.setOnClickListener(this); 77 | binding.rattingV.start4IV.setOnClickListener(this); 78 | binding.rattingV.start5IV.setOnClickListener(this); 79 | } 80 | 81 | private void clearRating() { 82 | binding.rattingV.start1IV.setImageResource(R.drawable.ic_unselected_start); 83 | binding.rattingV.start2IV.setImageResource(R.drawable.ic_unselected_start); 84 | binding.rattingV.start3IV.setImageResource(R.drawable.ic_unselected_start); 85 | binding.rattingV.start4IV.setImageResource(R.drawable.ic_unselected_start); 86 | binding.rattingV.start5IV.setImageResource(R.drawable.ic_unselected_start); 87 | } 88 | 89 | private void handleRating(int rating) { 90 | clearRating(); 91 | 92 | if (rating >= 1) { 93 | binding.rattingV.start1IV.setImageResource(R.drawable.ic_selected_star); 94 | } 95 | 96 | if (rating >= 2) { 97 | binding.rattingV.start2IV.setImageResource(R.drawable.ic_selected_star); 98 | } 99 | 100 | if (rating >= 3) { 101 | binding.rattingV.start3IV.setImageResource(R.drawable.ic_selected_star); 102 | } 103 | 104 | if (rating >= 4) { 105 | binding.rattingV.start4IV.setImageResource(R.drawable.ic_selected_star); 106 | } 107 | 108 | if (rating >= 5) { 109 | binding.rattingV.start5IV.setImageResource(R.drawable.ic_selected_star); 110 | } 111 | } 112 | 113 | @Override 114 | public void onClick(View view) { 115 | ST.showReviewDialog(mAct); 116 | if (view.equals(binding.rattingV.start1IV)) { 117 | handleRating(1); 118 | } else if (view.equals(binding.rattingV.start2IV)) { 119 | handleRating(2); 120 | } else if (view.equals(binding.rattingV.start3IV)) { 121 | handleRating(3); 122 | } else if (view.equals(binding.rattingV.start4IV)) { 123 | handleRating(4); 124 | } else if (view.equals(binding.rattingV.start5IV)) { 125 | handleRating(5); 126 | } 127 | } 128 | } 129 | 130 | 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/fragments/AboutFragment.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.fragments; 2 | 3 | import android.content.ActivityNotFoundException; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | 8 | import androidx.databinding.DataBindingUtil; 9 | 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.Toast; 14 | 15 | import com.saucelabs.mydemoapp.android.BuildConfig; 16 | import com.saucelabs.mydemoapp.android.R; 17 | import com.saucelabs.mydemoapp.android.databinding.FragmentAboutBinding; 18 | import com.saucelabs.mydemoapp.android.utils.Constants; 19 | import com.saucelabs.mydemoapp.android.utils.base.BaseFragment; 20 | 21 | 22 | public class AboutFragment extends BaseFragment implements View.OnClickListener { 23 | private FragmentAboutBinding binding; 24 | 25 | public static AboutFragment newInstance(String param1, String param2, int param3) { 26 | AboutFragment fragment = new AboutFragment(); 27 | Bundle args = new Bundle(); 28 | args.putString(Constants.ARG_PARAM1, param1); 29 | args.putString(Constants.ARG_PARAM2, param2); 30 | args.putInt(Constants.ARG_PARAM3, param3); 31 | fragment.setArguments(args); 32 | return fragment; 33 | } 34 | 35 | @Override 36 | public void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | mAct = getActivity(); 39 | if (getArguments() != null) { 40 | mParam1 = getArguments().getString(Constants.ARG_PARAM1, ""); 41 | mParam2 = getArguments().getString(Constants.ARG_PARAM2, ""); 42 | mParam3 = getArguments().getInt(Constants.ARG_PARAM3, -1); 43 | } 44 | } 45 | 46 | @Override 47 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 48 | Bundle savedInstanceState) { 49 | // Inflate the layout for this fragment 50 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_about, container, false); 51 | setListeners(); 52 | 53 | binding.versionTV.setText( 54 | String.format( 55 | "V.%s-build %d", 56 | BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE 57 | ) 58 | ); 59 | 60 | return binding.getRoot(); 61 | } 62 | 63 | private void setListeners() { 64 | binding.webTV.setOnClickListener(this); 65 | binding.versionTV.setOnClickListener(this); 66 | } 67 | 68 | @Override 69 | public void onClick(View v) { 70 | if(v.equals(binding.webTV)){ 71 | openWebPage("https://saucelabs.com/"); 72 | } else if (v.equals(binding.versionTV)) { 73 | openWebPage("https://github.com/saucelabs/my-demo-app-android/releases/tag/" + BuildConfig.VERSION_NAME); 74 | } 75 | } 76 | 77 | public void openWebPage(String url) { 78 | try { 79 | Uri webpage = Uri.parse(url); 80 | Intent myIntent = new Intent(Intent.ACTION_VIEW, webpage); 81 | requireActivity().startActivity(myIntent); 82 | } catch (ActivityNotFoundException e) { 83 | Toast.makeText(requireActivity(), "No application can handle this request. Please install a web browser or check your URL.", Toast.LENGTH_LONG).show(); 84 | e.printStackTrace(); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/fragments/CartFragment.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.fragments; 2 | 3 | import static com.saucelabs.mydemoapp.android.utils.Network.fetch; 4 | 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import androidx.annotation.Nullable; 11 | import androidx.databinding.DataBindingUtil; 12 | import androidx.recyclerview.widget.LinearLayoutManager; 13 | 14 | import com.saucelabs.mydemoapp.android.R; 15 | import com.saucelabs.mydemoapp.android.database.AppDatabase; 16 | import com.saucelabs.mydemoapp.android.databinding.FragmentCartBinding; 17 | import com.saucelabs.mydemoapp.android.utils.Constants; 18 | import com.saucelabs.mydemoapp.android.utils.base.BaseFragment; 19 | import com.saucelabs.mydemoapp.android.view.activities.MainActivity; 20 | import com.saucelabs.mydemoapp.android.view.adapters.CartItemAdapter; 21 | 22 | public class CartFragment extends BaseFragment implements View.OnClickListener { 23 | private FragmentCartBinding binding; 24 | CartItemAdapter adapter; 25 | 26 | public static CartFragment newInstance(String param1, String param2, int param3) { 27 | CartFragment fragment = new CartFragment(); 28 | Bundle args = new Bundle(); 29 | args.putString(Constants.ARG_PARAM1, param1); 30 | args.putString(Constants.ARG_PARAM2, param2); 31 | args.putInt(Constants.ARG_PARAM3, param3); 32 | fragment.setArguments(args); 33 | return fragment; 34 | } 35 | 36 | @Override 37 | public void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | mAct = getActivity(); 40 | if (getArguments() != null) { 41 | mParam1 = getArguments().getString(Constants.ARG_PARAM1, ""); 42 | mParam2 = getArguments().getString(Constants.ARG_PARAM2, ""); 43 | mParam3 = getArguments().getInt(Constants.ARG_PARAM3, -1); 44 | } 45 | } 46 | 47 | @Override 48 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 49 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_cart, container, false); 50 | bindData(); 51 | 52 | return binding.getRoot(); 53 | } 54 | 55 | @Override 56 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 57 | super.onActivityCreated(savedInstanceState); 58 | } 59 | 60 | private void bindData() { 61 | mDb = AppDatabase.getInstance(getActivity()); 62 | setListener(); 63 | setData(); 64 | checkLocalDataBase(); 65 | } 66 | 67 | private void setListener() { 68 | binding.shoppingBt.setOnClickListener(this); 69 | binding.cartBt.setOnClickListener(this); 70 | } 71 | 72 | private void checkLocalDataBase() { 73 | setAdapter(); 74 | // AppExecutors.getInstance().diskIO().execute(new Runnable() { 75 | // @Override 76 | // public void run() { 77 | // productList = mDb.personDao().getAllProducts(); 78 | // 79 | // } 80 | // }); 81 | } 82 | 83 | private void setAdapter() { 84 | binding.productRV.setLayoutManager(new LinearLayoutManager(mAct)); 85 | adapter = new CartItemAdapter(mAct, ST.cartItemList, true, (position, status) -> setData()); 86 | binding.productRV.setAdapter(adapter); 87 | } 88 | 89 | public void setData() { 90 | if (ST.cartItemList == null || ST.cartItemList.size() == 0) { 91 | binding.noItemCL.setVisibility(View.VISIBLE); 92 | binding.cartCL.setVisibility(View.GONE); 93 | } else { 94 | binding.noItemCL.setVisibility(View.GONE); 95 | binding.cartCL.setVisibility(View.VISIBLE); 96 | } 97 | 98 | binding.itemsTV.setText(ST.getTotalNum() + " " + getString(R.string.items)); 99 | binding.totalPriceTV.setText(ST.CURRENCY + " " + ST.getTotalPrice(ST.cartItemList)); 100 | binding.totalPriceTV.setText(ST.CURRENCY + " " + String.format("%.2f", ST.getTotalPrice(ST.cartItemList))); 101 | if (mAct instanceof MainActivity) { 102 | ((MainActivity) mAct).setData(); 103 | } 104 | } 105 | 106 | @Override 107 | public void onClick(View view) { 108 | if (view.equals(binding.shoppingBt)) { 109 | ST.startMainActivity(mAct, ST.getBundle(MainActivity.FRAGMENT_PRODUCT_CATAlOG, 1)); 110 | } else if (view.equals(binding.cartBt)) { 111 | if (ST.isLogin) { 112 | ST.startMainActivity(mAct, ST.getBundle(MainActivity.FRAGMENT_CHECKOUT_INFO, 1)); 113 | } else { 114 | Bundle bundle = ST.getBundle(MainActivity.FRAGMENT_LOGIN, 1); 115 | bundle.putString(Constants.ARG_PARAM1, ST.CHECKOUT); 116 | ST.startMainActivity(mAct, bundle); 117 | } 118 | 119 | fetch("https://my-demo-app.net/api/checkout"); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/fragments/CheckoutCompleteFragment.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.databinding.DataBindingUtil; 10 | 11 | import com.saucelabs.mydemoapp.android.R; 12 | import com.saucelabs.mydemoapp.android.database.AppDatabase; 13 | import com.saucelabs.mydemoapp.android.databinding.FragmentCheckoutCompleteBinding; 14 | import com.saucelabs.mydemoapp.android.utils.Constants; 15 | import com.saucelabs.mydemoapp.android.utils.base.BaseFragment; 16 | import com.saucelabs.mydemoapp.android.view.activities.MainActivity; 17 | 18 | import java.util.ArrayList; 19 | 20 | public class CheckoutCompleteFragment extends BaseFragment implements View.OnClickListener { 21 | private FragmentCheckoutCompleteBinding binding; 22 | 23 | public static CheckoutCompleteFragment newInstance(String param1, String param2, int param3) { 24 | CheckoutCompleteFragment fragment = new CheckoutCompleteFragment(); 25 | Bundle args = new Bundle(); 26 | args.putString(Constants.ARG_PARAM1, param1); 27 | args.putString(Constants.ARG_PARAM2, param2); 28 | args.putInt(Constants.ARG_PARAM3, param3); 29 | fragment.setArguments(args); 30 | return fragment; 31 | } 32 | 33 | @Override 34 | public void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | mAct = getActivity(); 37 | if (getArguments() != null) { 38 | mParam1 = getArguments().getString(Constants.ARG_PARAM1, ""); 39 | mParam2 = getArguments().getString(Constants.ARG_PARAM2, ""); 40 | mParam3 = getArguments().getInt(Constants.ARG_PARAM3, -1); 41 | } 42 | } 43 | 44 | @Override 45 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 46 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_checkout_complete, container, false); 47 | bindData(); 48 | 49 | return binding.getRoot(); 50 | } 51 | 52 | @Override 53 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 54 | super.onActivityCreated(savedInstanceState); 55 | } 56 | 57 | private void bindData() { 58 | mDb = AppDatabase.getInstance(getActivity()); 59 | init(); 60 | setListener(); 61 | 62 | } 63 | 64 | private void init(){ 65 | } 66 | 67 | private void setListener(){ 68 | binding.shoopingBt.setOnClickListener(this); 69 | } 70 | 71 | @Override 72 | public void onClick(View view) { 73 | if(view.equals(binding.shoopingBt)){ 74 | ST.cartItemList = new ArrayList<>(); 75 | ST.startActivity(mAct , MainActivity.class , ST.START_ACTIVITY_WITH_CLEAR_BACK_STACK); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/view/fragments/WebAddressFragment.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.view.fragments; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.databinding.DataBindingUtil; 6 | 7 | import android.text.TextUtils; 8 | import android.util.Patterns; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.webkit.URLUtil; 13 | 14 | import com.saucelabs.mydemoapp.android.R; 15 | import com.saucelabs.mydemoapp.android.databinding.FragmentWebAddressBinding; 16 | import com.saucelabs.mydemoapp.android.utils.Constants; 17 | import com.saucelabs.mydemoapp.android.utils.base.BaseFragment; 18 | import com.saucelabs.mydemoapp.android.view.activities.MainActivity; 19 | 20 | import java.net.MalformedURLException; 21 | import java.net.URL; 22 | import java.util.regex.Matcher; 23 | import java.util.regex.Pattern; 24 | 25 | public class WebAddressFragment extends BaseFragment implements View.OnClickListener { 26 | private FragmentWebAddressBinding binding; 27 | 28 | public static WebAddressFragment newInstance(String param1, String param2, int param3) { 29 | WebAddressFragment fragment = new WebAddressFragment(); 30 | Bundle args = new Bundle(); 31 | args.putString(Constants.ARG_PARAM1, param1); 32 | args.putString(Constants.ARG_PARAM2, param2); 33 | args.putInt(Constants.ARG_PARAM3, param3); 34 | fragment.setArguments(args); 35 | return fragment; 36 | } 37 | 38 | @Override 39 | public void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | mAct = getActivity(); 42 | if (getArguments() != null) { 43 | mParam1 = getArguments().getString(Constants.ARG_PARAM1, ""); 44 | mParam2 = getArguments().getString(Constants.ARG_PARAM2, ""); 45 | mParam3 = getArguments().getInt(Constants.ARG_PARAM3, -1); 46 | } 47 | 48 | } 49 | 50 | @Override 51 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 52 | Bundle savedInstanceState) { 53 | 54 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_web_address, container, false); 55 | init(); 56 | setListeners(); 57 | return binding.getRoot(); 58 | } 59 | 60 | private void init() { 61 | } 62 | 63 | private void setListeners() { 64 | binding.goBtn.setOnClickListener(this); 65 | } 66 | 67 | @Override 68 | public void onClick(View view) { 69 | if (view.equals(binding.goBtn)) { 70 | try { 71 | sendLink(); 72 | } catch (MalformedURLException e) { 73 | } 74 | } 75 | } 76 | 77 | private void sendLink() throws MalformedURLException { 78 | 79 | String urlString = binding.urlET.getText().toString().trim(); 80 | // if(!urlString.startsWith("https://")){ 81 | // if(!urlString.startsWith("http://")){ 82 | // urlString = "https://"+urlString; 83 | // } 84 | // } 85 | if (binding.urlET.getText().toString().trim().isEmpty()) { 86 | binding.urlErrorTV.setVisibility(View.VISIBLE); 87 | } else if (!isValidUrl(urlString)) { 88 | binding.urlErrorTV.setVisibility(View.VISIBLE); 89 | } else { 90 | 91 | binding.urlErrorTV.setVisibility(View.INVISIBLE); 92 | Bundle bundle = ST.getBundle(MainActivity.FRAGMENT_WEB_VIEW, 1); 93 | bundle.putString(Constants.ARG_PARAM1, urlString); 94 | ST.startMainActivity(mAct, bundle); 95 | binding.urlET.setText(""); 96 | } 97 | } 98 | 99 | private boolean isValidUrl(String url) { 100 | String WebUrl = "^((ftp|http|https):\\/\\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_-]+(\\.[a-zA-Z]+)+((\\/)[\\w#]+)*(\\/\\w+\\?[a-zA-Z0-9_]+=\\w+(&[a-zA-Z0-9_]+=\\w+)*)?$"; 101 | String website = url.trim(); 102 | if (website.trim().length() > 0) { 103 | if (!website.matches(WebUrl)) { 104 | //validation msg 105 | return false; 106 | } 107 | } 108 | return true; 109 | } 110 | } -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/viewModel/ProductCatalogViewModel.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.viewModel; 2 | 3 | import android.app.Application; 4 | import android.app.Fragment; 5 | 6 | import androidx.constraintlayout.widget.ConstraintLayout; 7 | import androidx.lifecycle.MutableLiveData; 8 | 9 | import com.saucelabs.mydemoapp.android.database.AppDatabase; 10 | import com.saucelabs.mydemoapp.android.database.AppExecutors; 11 | import com.saucelabs.mydemoapp.android.model.ProductModel; 12 | import com.saucelabs.mydemoapp.android.utils.DatabaseRepository; 13 | import com.saucelabs.mydemoapp.android.utils.SingletonClass; 14 | import com.saucelabs.mydemoapp.android.utils.base.BaseViewModel; 15 | import com.saucelabs.mydemoapp.android.view.activities.MainActivity; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Random; 20 | 21 | public class ProductCatalogViewModel extends BaseViewModel { 22 | private AppDatabase mDb; 23 | private DatabaseRepository repository; 24 | public MutableLiveData> allProducts = new MutableLiveData<>(); 25 | 26 | public ProductCatalogViewModel(Application app) { 27 | mDb = AppDatabase.getInstance(app); 28 | getAllProducts(MainActivity.NAME_ASC); 29 | } 30 | 31 | public void getAllProducts(int type) { 32 | AppExecutors.getInstance().diskIO().execute(new Runnable() { 33 | @Override 34 | public void run() { 35 | // allProducts.postValue(mDb.personDao().getAllProducts()); 36 | 37 | List productList = new ArrayList<>(); 38 | if (MainActivity.selectedSort == MainActivity.NAME_ASC) { 39 | productList = mDb.personDao().getPersonsSortByAscName(); 40 | } else if (MainActivity.selectedSort == MainActivity.NAME_DESC) { 41 | productList = mDb.personDao().getPersonsSortByDescName(); 42 | } else if (MainActivity.selectedSort == MainActivity.PRICE_ASC) { 43 | productList = mDb.personDao().getPersonsSortByAscPrice(); 44 | } else if (MainActivity.selectedSort == MainActivity.PRICE_DESC) { 45 | productList = mDb.personDao().getPersonsSortByDescPrice(); 46 | } 47 | 48 | // Alter prices if needed 49 | SingletonClass ST = SingletonClass.getInstance(); 50 | if (ST.hasVisualChanges) { 51 | productList = generateVisualChanges(productList); 52 | } 53 | allProducts.postValue(productList); 54 | } 55 | }); 56 | } 57 | 58 | private List generateVisualChanges(List productList) { 59 | Random random = new Random(); 60 | 61 | // Replaces prices by Random ones 62 | for (int i = 0; i < productList.size(); i++) { 63 | double randomPrice = 1 + (100 - 1) * random.nextDouble(); 64 | randomPrice = (double) Math.round(randomPrice * 100) / 100; 65 | productList.get(i).setPrice(randomPrice); 66 | } 67 | 68 | // Replace 2 first item by Onesie image. 69 | ProductModel onesie = findProductByName(productList, "Sauce Labs Onesie"); 70 | productList.get(0).setImage(onesie.getImage()); 71 | productList.get(0).setImageVal(onesie.getImageVal()); 72 | productList.get(1).setImage(onesie.getImage()); 73 | productList.get(1).setImageVal(onesie.getImageVal()); 74 | return productList; 75 | } 76 | 77 | private ProductModel findProductByName(List productList, String name) { 78 | for (ProductModel product: productList) { 79 | if (product.getTitle().equals(name)) { 80 | return product; 81 | } 82 | } 83 | return null; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/viewModel/ProductCatalogViewModelFactory.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.viewModel; 2 | 3 | import android.app.Application; 4 | 5 | import androidx.lifecycle.ViewModel; 6 | import androidx.lifecycle.ViewModelProvider; 7 | 8 | public class ProductCatalogViewModelFactory implements ViewModelProvider.Factory { 9 | 10 | private Application mApplication; 11 | private String mParam; 12 | 13 | 14 | public ProductCatalogViewModelFactory(Application application) { 15 | mApplication = application; 16 | 17 | } 18 | 19 | 20 | @Override 21 | public T create(Class modelClass) { 22 | return (T) new ProductCatalogViewModel(mApplication); 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/viewModel/ProductDetailViewModel.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.viewModel; 2 | 3 | import android.app.Application; 4 | 5 | import androidx.lifecycle.MutableLiveData; 6 | 7 | import com.saucelabs.mydemoapp.android.database.AppDatabase; 8 | import com.saucelabs.mydemoapp.android.database.AppExecutors; 9 | import com.saucelabs.mydemoapp.android.model.ProductModel; 10 | import com.saucelabs.mydemoapp.android.utils.DatabaseRepository; 11 | import com.saucelabs.mydemoapp.android.utils.base.BaseViewModel; 12 | 13 | public class ProductDetailViewModel extends BaseViewModel { 14 | private AppDatabase mDb; 15 | private DatabaseRepository repository; 16 | String id; 17 | public MutableLiveData product = new MutableLiveData<>(); 18 | 19 | public ProductDetailViewModel(Application app,String id) { 20 | mDb = AppDatabase.getInstance(app); 21 | this.id = id; 22 | getProduct(); 23 | } 24 | 25 | public void getProduct() { 26 | AppExecutors.getInstance().diskIO().execute(new Runnable() { 27 | @Override 28 | public void run() { 29 | 30 | product.postValue(mDb.personDao().getProduct(Integer.parseInt(id))); 31 | 32 | 33 | } 34 | }); 35 | } 36 | 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/viewModel/ProductDetailViewModelFactory.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.viewModel; 2 | 3 | import android.app.Application; 4 | 5 | import androidx.lifecycle.ViewModel; 6 | import androidx.lifecycle.ViewModelProvider; 7 | 8 | public class ProductDetailViewModelFactory implements ViewModelProvider.Factory { 9 | 10 | private Application mApplication; 11 | private String mParam; 12 | 13 | 14 | public ProductDetailViewModelFactory(Application application,String mParam) { 15 | mApplication = application; 16 | this.mParam = mParam; 17 | 18 | } 19 | 20 | 21 | @Override 22 | public T create(Class modelClass) { 23 | return (T) new ProductDetailViewModel(mApplication,mParam); 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/viewModel/SplashViewModel.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.viewModel; 2 | 3 | import android.app.Application; 4 | import android.os.Handler; 5 | import android.view.View; 6 | 7 | import androidx.lifecycle.MutableLiveData; 8 | 9 | import com.saucelabs.mydemoapp.android.database.AppDatabase; 10 | import com.saucelabs.mydemoapp.android.database.AppExecutors; 11 | import com.saucelabs.mydemoapp.android.model.ProductModel; 12 | import com.saucelabs.mydemoapp.android.utils.DatabaseRepository; 13 | import com.saucelabs.mydemoapp.android.utils.base.BaseViewModel; 14 | 15 | import java.util.List; 16 | 17 | public class SplashViewModel extends BaseViewModel { 18 | public MutableLiveData pb; 19 | private AppDatabase mDb; 20 | private DatabaseRepository repository; 21 | public MutableLiveData> allProducts = new MutableLiveData<>(); 22 | 23 | public SplashViewModel(Application app) { 24 | this.pb = new MutableLiveData<>(); 25 | pb.setValue(View.VISIBLE); 26 | // repository = new DatabaseRepository(app); 27 | // allNotes = repository.getAllNotes(); 28 | mDb = AppDatabase.getInstance(app); 29 | getAllProducts(); 30 | } 31 | 32 | public void addDelays() { 33 | new Handler().postDelayed(new Runnable() { 34 | @Override 35 | public void run() { 36 | pb.setValue(View.GONE); 37 | } 38 | }, 1000); 39 | } 40 | 41 | public void getAllProducts() { 42 | AppExecutors.getInstance().diskIO().execute(new Runnable() { 43 | @Override 44 | public void run() { 45 | allProducts.postValue(mDb.personDao().getAllProducts()); 46 | } 47 | }); 48 | } 49 | 50 | public void insertProducts(List list) { 51 | AppExecutors.getInstance().diskIO().execute(new Runnable() { 52 | @Override 53 | public void run() { 54 | mDb.personDao().insertProduct(list); 55 | pb.postValue(View.GONE); 56 | } 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/saucelabs/mydemoapp/android/viewModel/SplashViewModelFactory.java: -------------------------------------------------------------------------------- 1 | package com.saucelabs.mydemoapp.android.viewModel; 2 | 3 | import android.app.Application; 4 | 5 | import androidx.lifecycle.ViewModel; 6 | import androidx.lifecycle.ViewModelProvider; 7 | 8 | public class SplashViewModelFactory implements ViewModelProvider.Factory { 9 | // private static final String DEFAULT_LIMIT = "4"; 10 | // static Application application; 11 | // static String created="ViewModelFactorySuccess"; 12 | // 13 | // public SplashViewModelFactory(Application application, String created) { 14 | // 15 | // this.application=application; 16 | // 17 | // } 18 | // 19 | // 20 | // @NonNull 21 | // @Override 22 | // public T create(@NonNull Class modelClass) { 23 | // try { 24 | // return modelClass.getConstructor(SplashViewModel.class, int.class) 25 | // .newInstance(application, created); 26 | // } catch (NoSuchMethodException | IllegalAccessException | 27 | // InstantiationException | InvocationTargetException e) { 28 | // throw new RuntimeException("Cannot create an instance of " + modelClass, e); 29 | // } 30 | // } 31 | 32 | private Application mApplication; 33 | private String mParam; 34 | 35 | 36 | public SplashViewModelFactory(Application application) { 37 | mApplication = application; 38 | } 39 | 40 | 41 | @Override 42 | public T create(Class modelClass) { 43 | return (T) new SplashViewModel(mApplication); 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/res/anim/rotate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/loading_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable-xxhdpi/loading_icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/about_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/alert.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/back_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_my_list_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bot_graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/bot_graphic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bottom_radius_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/cart.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/cart_number_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/circle_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cursor_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/drawing_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/drawing_bg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit_bg_grey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit_bg_red.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/filled_grey_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/gradient_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/grey_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_black_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_black_circle.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_black_circle_side.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_blue_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_blue_circle.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_blue_circle_side.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_fb.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gray_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_gray_circle.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gray_circle_side.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_green_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_green_circle.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_green_circle_side.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_horse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_horse.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_in.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_minus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_plus.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product2_blue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product2_blue.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product2_green.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product2_green.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product2_red.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product2_red.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product2_yellow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product2_yellow.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product3.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product3_blue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product3_blue.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product3_brown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product3_brown.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product3_green.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product3_green.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product3_pink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product3_pink.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product3_red.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product3_red.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product4.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product4_green.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product4_green.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product4_orange.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product4_orange.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product4_red.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product4_red.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product4_violet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product4_violet.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product4_yellow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product4_yellow.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product5.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product5_pink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product5_pink.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product5_purple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product5_purple.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product5_turquoise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product5_turquoise.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product5_yellow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product5_yellow.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_product6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_product6.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_red_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_red_circle.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_red_circle_side.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_selected_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_selected_star.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_splash_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_splash_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_twitter.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_unselected_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/ic_unselected_start.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_touch_id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/icon_touch_id.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/item_bottom_view_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/login_button_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mastercard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/mastercard.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/my_button_background_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/not_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/price_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/price_asc.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/price_des.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/price_des.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/question.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/question.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/recipe_selction_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corner_add_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_fields_bg_orange.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/sort_asc.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/sort_des.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/sort_des.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_background_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_layout_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_layout_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/text_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/textview_states.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/title.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/un_selected_dot.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/unselected_radio_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/vector.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/visa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/drawable/visa.png -------------------------------------------------------------------------------- /app/src/main/res/font/bold_font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/font/bold_font.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/light_font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/font/light_font.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/regular_font.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/font/regular_font.otf -------------------------------------------------------------------------------- /app/src/main/res/font/regular_font_.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saucelabs/my-demo-app-android/8cf5fac23ca6cedafe7be3c63fad8fe4ee6f5612/app/src/main/res/font/regular_font_.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_debug_crash.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 15 | 16 | 26 | 27 | 32 | 33 | 40 | 41 |