├── core ├── .gitignore ├── consumer-rules.pro ├── src │ └── main │ │ ├── res │ │ ├── drawable │ │ │ ├── ic_logo.png │ │ │ ├── product_img_example.png │ │ │ ├── profile_avatar_placeholder_large.png │ │ │ ├── searchview_background.xml │ │ │ ├── bottom_sheet_dialog_background.xml │ │ │ ├── ic_code.xml │ │ │ ├── button_gradient.xml │ │ │ ├── button_small_gradient_background.xml │ │ │ ├── ic_close.xml │ │ │ ├── ic_arrow_back.xml │ │ │ ├── ic_card.xml │ │ │ ├── ic_tune.xml │ │ │ ├── ic_sign_out.xml │ │ │ ├── ic_broom.xml │ │ │ ├── ic_crescent.xml │ │ │ ├── ic_cart.xml │ │ │ ├── ic_bug.xml │ │ │ └── button_gradient_inactive.xml │ │ ├── font │ │ │ ├── montserrat_bold_ttf.ttf │ │ │ ├── montserrat_medium_ttf.ttf │ │ │ ├── montserrat_regular_ttf.ttf │ │ │ └── montserrat_semibold_ttf.ttf │ │ ├── values │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── colors.xml │ │ └── layout │ │ │ ├── list_item_recycler.xml │ │ │ ├── searchview_custom.xml │ │ │ └── layout_progress_bar.xml │ │ ├── java │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ └── core │ │ │ ├── util │ │ │ ├── Constants.kt │ │ │ ├── Log.kt │ │ │ └── FlowUtils.kt │ │ │ └── navigation │ │ │ ├── destination │ │ │ ├── CartDestination.kt │ │ │ ├── HomeDestination.kt │ │ │ ├── UserDestination.kt │ │ │ ├── CatalogDestination.kt │ │ │ ├── OrdersDestination.kt │ │ │ ├── CheckoutDestination.kt │ │ │ ├── SignUpDestination.kt │ │ │ ├── ProductDetailsDestination.kt │ │ │ ├── NavDestination.kt │ │ │ └── SignInDestination.kt │ │ │ ├── FragmentExt.kt │ │ │ └── MarketplaceNavigator.kt │ │ └── AndroidManifest.xml └── proguard-rules.pro ├── data ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ └── data │ │ │ ├── mapper │ │ │ ├── DepartmentMapper.kt │ │ │ ├── CartItemMapper.kt │ │ │ ├── ProductPreviewMapper.kt │ │ │ └── OrderMapper.kt │ │ │ ├── model │ │ │ ├── CartItemBean.kt │ │ │ ├── ProductPreviewBean.kt │ │ │ └── OrderBean.kt │ │ │ ├── DummyProducts.kt │ │ │ └── DepartmentRepositoryImpl.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── narcissus │ │ └── marketplace │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── domain ├── .gitignore ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ └── domain │ │ │ ├── model │ │ │ ├── Cart.kt │ │ │ ├── orders │ │ │ │ ├── OrderPaymentStatus.kt │ │ │ │ ├── OrderStatus.kt │ │ │ │ ├── Order.kt │ │ │ │ ├── OrderItem.kt │ │ │ │ └── OrderPaymentResult.kt │ │ │ ├── SpecialOfferBanner.kt │ │ │ ├── UserProfile.kt │ │ │ ├── Department.kt │ │ │ ├── Review.kt │ │ │ ├── ProductOfTheDay.kt │ │ │ ├── SimilarProduct.kt │ │ │ ├── User.kt │ │ │ ├── ProductPreview.kt │ │ │ ├── CheckoutItem.kt │ │ │ ├── CartItem.kt │ │ │ └── ProductDetails.kt │ │ │ ├── auth │ │ │ ├── Patterns.kt │ │ │ ├── SignOutResult.kt │ │ │ ├── AuthState.kt │ │ │ ├── SignInResult.kt │ │ │ ├── SignUpResult.kt │ │ │ └── PasswordRequirement.kt │ │ │ ├── card │ │ │ ├── CardPatterns.kt │ │ │ └── CardValidationResult.kt │ │ │ ├── repository │ │ │ ├── DepartmentRepository.kt │ │ │ ├── ProductsDetailsRepository.kt │ │ │ ├── OrderRepository.kt │ │ │ ├── CartRepository.kt │ │ │ ├── UserRepository.kt │ │ │ └── ProductsPreviewRepository.kt │ │ │ ├── usecase │ │ │ ├── GetCart.kt │ │ │ ├── SignOut.kt │ │ │ ├── GetOrderList.kt │ │ │ ├── GetUserData.kt │ │ │ ├── GetAuthStateFlow.kt │ │ │ ├── GetCartCost.kt │ │ │ ├── GetSelectedCartItems.kt │ │ │ ├── GetDepartments.kt │ │ │ ├── GetRecentlyVisitedProducts.kt │ │ │ ├── RemoveSelectedCartItems.kt │ │ │ ├── SignInWithGoogle.kt │ │ │ ├── GetIsUserAuthenticated.kt │ │ │ ├── GetAllProducts.kt │ │ │ ├── SelectAllCartItems.kt │ │ │ ├── GetRandomProducts.kt │ │ │ ├── GetTopRatedProducts.kt │ │ │ ├── GetTopSalesProducts.kt │ │ │ ├── GetPeopleAreBuyingProducts.kt │ │ │ ├── RemoveFromCart.kt │ │ │ ├── GetSimilarProducts.kt │ │ │ ├── GetProductsByDepartmentId.kt │ │ │ ├── RestoreCartItems.kt │ │ │ ├── SetCartItemAmount.kt │ │ │ ├── GetTopRatedProductsByDepartment.kt │ │ │ ├── GetTopSalesProductsByDepartment.kt │ │ │ ├── SetCartItemSelected.kt │ │ │ ├── GetCheckout.kt │ │ │ ├── GetCartItemsAmount.kt │ │ │ ├── SignInWithEmail.kt │ │ │ ├── GetCartCostFlow.kt │ │ │ ├── AddToCart.kt │ │ │ ├── GetProductDetails.kt │ │ │ ├── SignUpWithEmail.kt │ │ │ ├── AddCard.kt │ │ │ └── MakeAnOrder.kt │ │ │ └── util │ │ │ ├── ActionResult.kt │ │ │ └── SearchParams.kt │ └── test │ │ └── java │ │ └── com │ │ └── narcissus │ │ └── marketplace │ │ ├── RemoveSelectedCartItems.kt │ │ ├── RemoveFromCartTest.kt │ │ ├── SelectAllCartItemsTest.kt │ │ ├── GetCartTest.kt │ │ ├── SetCartItemAmountTest.kt │ │ ├── SetCartItemSelectedTest.kt │ │ ├── GetRandomProductsTest.kt │ │ ├── GetTopRatedProductsTest.kt │ │ ├── GetTopSalesProductsTest.kt │ │ ├── GetRecentlyVisitedProductsTest.kt │ │ └── AddToCartTest.kt └── build.gradle ├── apiclient ├── .gitignore ├── consumer-rules.pro ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ └── apiclient │ │ │ ├── di │ │ │ └── Qualifiers.kt │ │ │ ├── Constants.kt │ │ │ └── api │ │ │ ├── service │ │ │ └── OrderApiService.kt │ │ │ ├── model │ │ │ ├── DepartmentsResponse.kt │ │ │ ├── OrderData.kt │ │ │ ├── ProductPreviewsResponse.kt │ │ │ └── SerializedNames.kt │ │ │ └── interceptor │ │ │ └── CacheInterceptor.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── firebase ├── .gitignore ├── consumer-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── narcissus │ │ └── marketplace │ │ └── data │ │ └── firebase │ │ ├── di │ │ ├── Qualifiers.kt │ │ └── FirebaseModule.kt │ │ └── Constants.kt ├── proguard-rules.pro └── build.gradle ├── persistence ├── .gitignore ├── consumer-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── narcissus │ │ └── marketplace │ │ └── data │ │ └── persistence │ │ ├── database │ │ ├── AppDatabase.kt │ │ └── ProductDao.kt │ │ ├── model │ │ └── ProductEntity.kt │ │ └── di │ │ └── PersistenceModule.kt ├── proguard-rules.pro └── build.gradle ├── ui ├── cart │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── narcissus │ │ │ │ └── marketplace │ │ │ │ └── ui │ │ │ │ └── cart │ │ │ │ ├── di │ │ │ │ └── CartQualifiers.kt │ │ │ │ └── checkout │ │ │ │ ├── OrderConstants.kt │ │ │ │ └── CheckoutScreenState.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ ├── rounded_shape.xml │ │ │ ├── button_background_selector.xml │ │ │ ├── background_button_disabled.xml │ │ │ ├── custom_checkbox.xml │ │ │ ├── ic_add.xml │ │ │ ├── ic_subtract.xml │ │ │ ├── ic_check_arrow.xml │ │ │ ├── checkbox_unchecked.xml │ │ │ ├── checkbox_checked.xml │ │ │ ├── solid_stroke.xml │ │ │ ├── ic_visa.xml │ │ │ └── ic_mastercard.xml │ │ │ ├── values │ │ │ └── dimens.xml │ │ │ └── navigation │ │ │ └── nav_graph_cart.xml │ └── proguard-rules.pro ├── catalog │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── narcissus │ │ │ │ └── marketplace │ │ │ │ └── ui │ │ │ │ └── catalog │ │ │ │ ├── di │ │ │ │ └── CatalogModule.kt │ │ │ │ └── CatalogViewModel.kt │ │ │ └── res │ │ │ ├── navigation │ │ │ └── nav_graph_catalog.xml │ │ │ └── layout │ │ │ └── fragment_catalog.xml │ └── proguard-rules.pro ├── home │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── values │ │ │ │ ├── integers.xml │ │ │ │ └── strings.xml │ │ │ ├── layout │ │ │ │ ├── tab_view.xml │ │ │ │ ├── fragment_home_screen_page.xml │ │ │ │ ├── list_item_headline.xml │ │ │ │ ├── list_item_banner.xml │ │ │ │ └── list_item_featured_content.xml │ │ │ ├── navigation │ │ │ │ └── nav_graph_home.xml │ │ │ └── animator │ │ │ │ ├── decrease_tab.xml │ │ │ │ └── increase_tab.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ └── ui │ │ │ └── home │ │ │ ├── recycler │ │ │ ├── FeaturedTab.kt │ │ │ └── ProductsOfTheDayAdapter.kt │ │ │ ├── util │ │ │ └── TextViewUtils.kt │ │ │ └── di │ │ │ └── HomeModule.kt │ └── proguard-rules.pro ├── search │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── res │ │ │ ├── navigation │ │ │ │ └── nav_graph_search.xml │ │ │ └── layout │ │ │ │ └── fragment_search_history.xml │ │ │ └── java │ │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ └── ui │ │ │ └── search │ │ │ └── SearchFragment.kt │ ├── proguard-rules.pro │ └── build.gradle ├── sign_in │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── ic_google.png │ │ │ │ └── button_google_background.xml │ │ │ └── navigation │ │ │ │ └── nav_graph_sign_in.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── narcissus │ │ │ │ └── marketplace │ │ │ │ └── ui │ │ │ │ └── sign_in │ │ │ │ ├── di │ │ │ │ └── SignInQualifiers.kt │ │ │ │ ├── sign_up │ │ │ │ └── SignUpViewModel.kt │ │ │ │ └── SignInViewModel.kt │ │ │ └── AndroidManifest.xml │ └── proguard-rules.pro ├── splash │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── res │ │ │ └── navigation │ │ │ │ └── nav_graph_splash.xml │ │ │ └── java │ │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ └── ui │ │ │ └── splash │ │ │ └── SplashViewModel.kt │ └── proguard-rules.pro ├── user │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ └── navigation │ │ │ │ └── nav_graph_user.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ └── ui │ │ │ └── user │ │ │ ├── orders │ │ │ ├── OrdersSideEffect.kt │ │ │ ├── OrdersState.kt │ │ │ └── OrdersViewModel.kt │ │ │ ├── UserState.kt │ │ │ ├── theme │ │ │ ├── Shape.kt │ │ │ ├── Dimen.kt │ │ │ ├── Font.kt │ │ │ ├── Color.kt │ │ │ └── Type.kt │ │ │ ├── UserSideEffect.kt │ │ │ └── di │ │ │ └── UserModule.kt │ └── proguard-rules.pro └── product_details │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── dimens.xml │ │ │ └── strings.xml │ │ ├── drawable │ │ │ ├── reviews_recyclerview_divider.xml │ │ │ ├── recycler_view_divider.xml │ │ │ └── dotted_line.xml │ │ ├── layout │ │ │ ├── list_item_details_main_info.xml │ │ │ ├── list_item_details_main_info_placeholder.xml │ │ │ ├── list_item_details_product_placeholder.xml │ │ │ ├── list_item_details_price.xml │ │ │ ├── list_item_product_preview_loading.xml │ │ │ ├── list_item_details_titile_basic.xml │ │ │ ├── list_item_details_divider.xml │ │ │ ├── list_item_details_title_button.xml │ │ │ └── list_item_details_product_about_multiple_lines.xml │ │ └── anim │ │ │ ├── slide_in_left.xml │ │ │ ├── slide_in_right.xml │ │ │ ├── slide_out_left.xml │ │ │ └── slide_out_right.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── narcissus │ │ └── marketplace │ │ └── ui │ │ └── product_details │ │ ├── model │ │ ├── ToolbarData.kt │ │ └── ParcelableReview.kt │ │ ├── di │ │ └── ProductDetailsModule.kt │ │ ├── ProductsAdapter.kt │ │ ├── utils │ │ └── GetTextLinearGradient.kt │ │ ├── main_info_recycler_view │ │ └── ProductMainInfoAdapter.kt │ │ └── reviews │ │ └── DividerDecoration.kt │ └── proguard-rules.pro ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── app ├── src │ └── main │ │ ├── ic_launcher-playstore.png │ │ ├── res │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── drawable │ │ │ ├── profile_avatar_placeholder_large.png │ │ │ ├── rectangle_back.xml │ │ │ ├── ic_arrow_forward_ios_black_36dp.xml │ │ │ ├── ic_arrow_back_black_36dp.xml │ │ │ ├── ic_home.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_catalog.xml │ │ │ ├── ic_user.xml │ │ │ └── ic_cart.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── color │ │ │ └── background_search_tint.xml │ │ ├── menu │ │ │ └── bottom_navigation.xml │ │ ├── navigation │ │ │ └── nav_graph.xml │ │ ├── values-night │ │ │ └── themes.xml │ │ └── values │ │ │ └── themes.xml │ │ ├── java │ │ └── com │ │ │ └── narcissus │ │ │ └── marketplace │ │ │ ├── di │ │ │ └── AppModule.kt │ │ │ └── ui │ │ │ └── MarketplaceApp.kt │ │ └── AndroidManifest.xml ├── .gitignore └── proguard-rules.pro ├── .gitignore ├── .cspell.json ├── settings.gradle ├── .mega-linter.yml ├── gradle.properties └── .github └── workflows └── android_deploy_to_firebase.yml /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /apiclient/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /apiclient/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /firebase/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /firebase/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /persistence/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/cart/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/cart/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/catalog/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/home/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/home/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/search/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/search/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/sign_in/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/splash/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/splash/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/user/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/user/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /persistence/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/catalog/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/sign_in/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/product_details/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ui/product_details/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/core/src/main/res/drawable/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /core/src/main/res/font/montserrat_bold_ttf.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/core/src/main/res/font/montserrat_bold_ttf.ttf -------------------------------------------------------------------------------- /ui/sign_in/src/main/res/drawable/ic_google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/ui/sign_in/src/main/res/drawable/ic_google.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /core/src/main/res/drawable/product_img_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/core/src/main/res/drawable/product_img_example.png -------------------------------------------------------------------------------- /core/src/main/res/font/montserrat_medium_ttf.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/core/src/main/res/font/montserrat_medium_ttf.ttf -------------------------------------------------------------------------------- /core/src/main/res/font/montserrat_regular_ttf.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/core/src/main/res/font/montserrat_regular_ttf.ttf -------------------------------------------------------------------------------- /core/src/main/res/font/montserrat_semibold_ttf.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/core/src/main/res/font/montserrat_semibold_ttf.ttf -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /ui/user/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Source code 4 | 5 | -------------------------------------------------------------------------------- /ui/home/src/main/res/values/integers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 150 4 | 5 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/Cart.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class Cart( 4 | val data: List 5 | ) 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/profile_avatar_placeholder_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/app/src/main/res/drawable/profile_avatar_placeholder_large.png -------------------------------------------------------------------------------- /ui/product_details/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 150dp 4 | 5 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/util/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.util 2 | 3 | object Constants { 4 | const val THEME_KEY = "THEME_KEY" 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/profile_avatar_placeholder_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beleavemebe/marketplace-app/HEAD/core/src/main/res/drawable/profile_avatar_placeholder_large.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | google-services.json 12 | /report 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/auth/Patterns.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.auth 2 | 3 | object Patterns { 4 | const val EMAIL = "^[A-Za-z](.*)([@])(.+)(\\.)(.+)" 5 | } 6 | -------------------------------------------------------------------------------- /ui/sign_in/src/main/java/com/narcissus/marketplace/ui/sign_in/di/SignInQualifiers.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.sign_in.di 2 | 3 | object SignInQualifiers { 4 | object DefaultWebClientId 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /apiclient/src/main/java/com/narcissus/marketplace/apiclient/di/Qualifiers.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.apiclient.di 2 | 3 | object Qualifiers { 4 | object OrderApiService 5 | object ContentApiService 6 | } 7 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/card/CardPatterns.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.card 2 | 3 | object CardPatterns { 4 | const val CARD_HOLDER_NAME = "^((?:[A-Za-z]+ ?){1,3})\$" 5 | } 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/orders/OrderPaymentStatus.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model.orders 2 | 3 | enum class OrderPaymentStatus { 4 | PAID, 5 | CANCELLED 6 | } 7 | -------------------------------------------------------------------------------- /ui/cart/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/home/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/home/src/main/java/com/narcissus/marketplace/ui/home/recycler/FeaturedTab.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.home.recycler 2 | 3 | enum class FeaturedTab { 4 | TOP_RATED, 5 | TOP_SALES, 6 | EXPLORE 7 | } 8 | -------------------------------------------------------------------------------- /ui/user/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /apiclient/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /firebase/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/catalog/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/search/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/sign_in/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/splash/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/orders/OrdersSideEffect.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user.orders 2 | 3 | sealed class OrdersSideEffect { 4 | object NavigateUp : OrdersSideEffect() 5 | } 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/SpecialOfferBanner.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class SpecialOfferBanner( 4 | val imgUrl: String, 5 | val destinationLink: String, 6 | ) 7 | -------------------------------------------------------------------------------- /persistence/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/product_details/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/auth/SignOutResult.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.auth 2 | 3 | sealed class SignOutResult { 4 | object Error : SignOutResult() 5 | object Success : SignOutResult() 6 | } 7 | -------------------------------------------------------------------------------- /firebase/src/main/java/com/narcissus/marketplace/data/firebase/di/Qualifiers.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.firebase.di 2 | 3 | object Qualifiers { 4 | object CartReference 5 | object OrdersReference 6 | object UserUid 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/util/Log.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.util 2 | 3 | import android.util.Log 4 | 5 | @Suppress("unused") 6 | inline fun Any.log(msg: () -> Any?) = Log.d("marketplace-debug", msg().toString()) 7 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/orders/OrderStatus.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model.orders 2 | 3 | enum class OrderStatus { 4 | ACCEPTED, 5 | COMPLETED, 6 | INDELEVERING, 7 | CANCELED, 8 | } 9 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/CartDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | object CartDestination : NavDestination { 4 | override val url = "marketplace-app://cart" 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/HomeDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | object HomeDestination : NavDestination { 4 | override val url = "marketplace-app://home" 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/UserDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | object UserDestination : NavDestination { 4 | override val url = "marketplace-app://user" 5 | } 6 | -------------------------------------------------------------------------------- /ui/product_details/src/main/java/com/narcissus/marketplace/ui/product_details/model/ToolbarData.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.product_details.model 2 | 3 | data class ToolbarData( 4 | val productIcon: String, 5 | val productName: String, 6 | ) 7 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/UserProfile.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class UserProfile( 4 | val id: String, 5 | val name: String?, 6 | val email: String, 7 | val iconUrl: String? 8 | ) 9 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/CatalogDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | object CatalogDestination : NavDestination { 4 | override val url = "marketplace-app://catalog" 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/OrdersDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | object OrdersDestination : NavDestination { 4 | override val url = "marketplace-app://user/orders" 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/CheckoutDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | object CheckoutDestination : NavDestination { 4 | override val url = "marketplace-app://cart/checkout" 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/SignUpDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | object SignUpDestination : NavDestination { 4 | override val url: String = "marketplace-app://sign-up" 5 | } 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/Department.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class Department( 4 | val departmentId: String, 5 | val name: String, 6 | val productsAmount: Int, 7 | val imageUrl: String, 8 | ) 9 | -------------------------------------------------------------------------------- /ui/cart/src/main/java/com/narcissus/marketplace/ui/cart/di/CartQualifiers.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.cart.di 2 | 3 | object CartQualifiers { 4 | object PaymentInProgressNotification 5 | object PaymentOneTimeRequest 6 | object PaymentWorkerInputData 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/searchview_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Feb 09 17:38:01 MSK 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /core/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8dp 4 | 12dp 5 | 16dp 6 | 32dp 7 | 8 | -------------------------------------------------------------------------------- /ui/home/src/main/java/com/narcissus/marketplace/ui/home/util/TextViewUtils.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.home.util 2 | 3 | import android.graphics.Paint 4 | import android.widget.TextView 5 | 6 | fun TextView.crossOut() { 7 | paintFlags = paintFlags or Paint.STRIKE_THRU_TEXT_FLAG 8 | } 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/repository/DepartmentRepository.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.repository 2 | 3 | import com.narcissus.marketplace.domain.model.Department 4 | 5 | interface DepartmentRepository { 6 | suspend fun getDepartments(): List 7 | } 8 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/rounded_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/ProductDetailsDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | class ProductDetailsDestination(productId: String) : NavDestination { 4 | override val url = "marketplace-app://product/$productId" 5 | } 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/Review.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class Review( 4 | val reviewId: String, 5 | val author: String, 6 | val details: String, 7 | val rating: Int, 8 | val reviewAuthorIcon: String, 9 | ) 10 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/drawable/reviews_recyclerview_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/orders/OrdersState.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user.orders 2 | 3 | import com.narcissus.marketplace.domain.model.orders.Order 4 | 5 | data class OrdersState( 6 | val isLoading: Boolean, 7 | val orders: List = emptyList() 8 | ) 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetCart.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.CartRepository 4 | 5 | class GetCart(private val cartRepository: CartRepository) { 6 | operator fun invoke() = cartRepository.getCart() 7 | } 8 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/button_background_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePaths": [ 3 | "**/node_modules/**", 4 | "**/vscode-extension/**", 5 | "**/.git/**", 6 | ".vscode", 7 | "megalinter", 8 | "package-lock.json", 9 | "report" 10 | ], 11 | "language": "en", 12 | "noConfigSearch": true, 13 | "words": [], 14 | "version": "0.2" 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/NavDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | import android.net.Uri 4 | 5 | sealed interface NavDestination { 6 | val url: String 7 | } 8 | 9 | val NavDestination.uri: Uri 10 | get() = Uri.parse(url) 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/SignOut.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.UserRepository 4 | 5 | class SignOut(private val userRepository: UserRepository) { 6 | suspend operator fun invoke() = userRepository.signOut() 7 | } 8 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/background_button_disabled.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/color/background_search_tint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/destination/SignInDestination.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation.destination 2 | 3 | class SignInDestination(hasNavigatedFromUserScreen: Boolean) : NavDestination { 4 | override val url: String = "marketplace-app://sign-in/$hasNavigatedFromUserScreen" 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/bottom_sheet_dialog_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/ProductOfTheDay.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class ProductOfTheDay( 4 | val id: String, 5 | val imageUrl: String, 6 | val name: String, 7 | val oldPrice: Int, 8 | val newPrice: Int, 9 | val percentOff: Int, 10 | ) 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetOrderList.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.OrderRepository 4 | 5 | class GetOrderList(private val orderRepository: OrderRepository) { 6 | operator fun invoke() = orderRepository.getOrders() 7 | } 8 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/custom_checkbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/repository/ProductsDetailsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.repository 2 | 3 | import com.narcissus.marketplace.domain.model.ProductDetails 4 | 5 | interface ProductsDetailsRepository { 6 | suspend fun getProductDetailsById(productId: String): ProductDetails 7 | } 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetUserData.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.UserRepository 4 | 5 | class GetUserData(private val userRepository: UserRepository) { 6 | suspend operator fun invoke() = userRepository.getUserData() 7 | } 8 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/drawable/recycler_view_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/UserState.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user 2 | 3 | import com.narcissus.marketplace.domain.model.UserProfile 4 | 5 | data class UserState( 6 | val isLoading: Boolean, 7 | val isUserAuthenticated: Boolean? = null, 8 | val user: UserProfile? = null, 9 | ) 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetAuthStateFlow.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.UserRepository 4 | 5 | class GetAuthStateFlow(private val userRepository: UserRepository) { 6 | operator fun invoke() = userRepository.authStateFlow 7 | } 8 | -------------------------------------------------------------------------------- /firebase/src/main/java/com/narcissus/marketplace/data/firebase/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.firebase 2 | 3 | internal object Constants { 4 | const val DATABASE_URL = "https://epam-marketplace-app-default-rtdb.europe-west1.firebasedatabase.app/" 5 | const val CHILD_CART = "cart" 6 | const val CHILD_ORDERS = "orders" 7 | } 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetCartCost.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.CartRepository 4 | 5 | class GetCartCost(private val cartRepository: CartRepository) { 6 | suspend operator fun invoke() = 7 | cartRepository.getCartCost() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rectangle_back.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/orders/Order.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model.orders 2 | 3 | import java.util.Date 4 | 5 | data class Order( 6 | val id: String, 7 | val number: Int, 8 | val date: Date, 9 | val status: OrderStatus, 10 | val summaryPrice: Int, 11 | val items: List, 12 | ) 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetSelectedCartItems.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.CartRepository 4 | 5 | class GetSelectedCartItems(private val cartRepository: CartRepository) { 6 | suspend operator fun invoke() = cartRepository.getSelectedCartItems() 7 | } 8 | -------------------------------------------------------------------------------- /ui/sign_in/src/main/res/drawable/button_google_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/SimilarProduct.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class SimilarProduct( 4 | val id: String, 5 | val icon: String, 6 | val price: Int, 7 | val name: String, 8 | val category: String, 9 | val type: String, 10 | val stock: Int, 11 | val rating: Int, 12 | ) 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetDepartments.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.DepartmentRepository 4 | 5 | class GetDepartments(private val departmentsRepository: DepartmentRepository) { 6 | suspend operator fun invoke() = departmentsRepository.getDepartments() 7 | } 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetRecentlyVisitedProducts.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.UserRepository 4 | 5 | class GetRecentlyVisitedProducts(private val userRepository: UserRepository) { 6 | operator fun invoke() = userRepository.recentlyVisitedProducts 7 | } 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/RemoveSelectedCartItems.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.CartRepository 4 | 5 | class RemoveSelectedCartItems(private val cartRepository: CartRepository) { 6 | suspend operator fun invoke() = cartRepository.deleteSelectedItems() 7 | } 8 | -------------------------------------------------------------------------------- /ui/catalog/src/main/java/com/narcissus/marketplace/ui/catalog/di/CatalogModule.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.catalog.di 2 | 3 | import com.narcissus.marketplace.ui.catalog.CatalogViewModel 4 | import org.koin.androidx.viewmodel.dsl.viewModel 5 | import org.koin.dsl.module 6 | 7 | val catalogModule = module { 8 | viewModel { CatalogViewModel(get()) } 9 | } 10 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/drawable/dotted_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | 6 | val Shapes = Shapes( 7 | small = RoundedCornerShape(HalfPadding / 2), 8 | medium = RoundedCornerShape(HalfPadding), 9 | ) 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/auth/AuthState.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.auth 2 | 3 | import com.narcissus.marketplace.domain.model.UserProfile 4 | 5 | sealed class AuthState { 6 | object Unknown : AuthState() 7 | object NotAuthenticated : AuthState() 8 | data class Authenticated(val user: UserProfile?) : AuthState() 9 | } 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/SignInWithGoogle.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.UserRepository 4 | 5 | class SignInWithGoogle(private val userRepository: UserRepository) { 6 | suspend operator fun invoke(idToken: String) = userRepository.signInWithGoogle(idToken) 7 | } 8 | -------------------------------------------------------------------------------- /ui/home/src/main/java/com/narcissus/marketplace/ui/home/di/HomeModule.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.home.di 2 | 3 | import com.narcissus.marketplace.ui.home.HomeViewModel 4 | import org.koin.androidx.viewmodel.dsl.viewModel 5 | import org.koin.dsl.module 6 | 7 | val homeModule = module { 8 | viewModel { HomeViewModel(get(), get(), get(), get(), get()) } 9 | } 10 | -------------------------------------------------------------------------------- /ui/home/src/main/res/layout/tab_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetIsUserAuthenticated.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.UserRepository 4 | 5 | class GetIsUserAuthenticated(private val userRepository: UserRepository) { 6 | suspend operator fun invoke() = 7 | userRepository.isUserAuthenticated() 8 | } 9 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/theme/Dimen.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user.theme 2 | 3 | import androidx.compose.ui.unit.dp 4 | 5 | val IconSize = 18.dp 6 | 7 | val SmallPadding = 4.dp 8 | val HalfPadding = 8.dp 9 | val IntermediatePadding = 12.dp 10 | val DefaultPadding = 16.dp 11 | 12 | val HeaderHeight = 56.dp 13 | val ItemHeight = 40.dp 14 | -------------------------------------------------------------------------------- /apiclient/src/main/java/com/narcissus/marketplace/apiclient/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.apiclient 2 | 3 | object Constants { 4 | internal const val BASE_URL = "https://api-narcissus-marketplace.herokuapp.com/" 5 | internal const val CACHE_DIR = "http-cache" 6 | internal const val CACHE_SIZE = 10L * 1024L * 1024L 7 | internal const val CACHE_MAX_AGE = 3 8 | } 9 | -------------------------------------------------------------------------------- /domain/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | id 'org.jetbrains.kotlin.jvm' 4 | } 5 | 6 | java { 7 | sourceCompatibility = JavaVersion.VERSION_1_7 8 | targetCompatibility = JavaVersion.VERSION_1_7 9 | } 10 | dependencies { 11 | implementation(Coroutines.coroutinesCore) 12 | testImplementation(Junit.junit) 13 | testImplementation(Mockk.mockk) 14 | } 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetAllProducts.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 4 | 5 | class GetAllProducts(private val productsPreviewRepository: ProductsPreviewRepository) { 6 | suspend operator fun invoke() = productsPreviewRepository.getProducts() 7 | } 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/SelectAllCartItems.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.CartRepository 4 | 5 | class SelectAllCartItems(private val cartRepository: CartRepository) { 6 | suspend operator fun invoke(isSelected: Boolean) = cartRepository.selectAllCartItems(isSelected) 7 | } 8 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/layout/list_item_details_main_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /data/src/main/java/com/narcissus/marketplace/data/mapper/DepartmentMapper.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.mapper 2 | 3 | import com.narcissus.marketplace.apiclient.api.model.DepartmentResponseData 4 | import com.narcissus.marketplace.domain.model.Department 5 | 6 | fun DepartmentResponseData.toDepartment() = 7 | Department( 8 | id, name, numberOfProducts, imageUrl, 9 | ) 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetRandomProducts.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 4 | 5 | class GetRandomProducts(private val productsPreviewRepository: ProductsPreviewRepository) { 6 | suspend operator fun invoke() = productsPreviewRepository.getProductsRandom() 7 | } 8 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/ic_subtract.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_forward_ios_black_36dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetTopRatedProducts.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 4 | 5 | class GetTopRatedProducts(private val productsPreviewRepository: ProductsPreviewRepository) { 6 | suspend operator fun invoke() = productsPreviewRepository.getProductsTopRated() 7 | } 8 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetTopSalesProducts.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 4 | 5 | class GetTopSalesProducts(private val productsPreviewRepository: ProductsPreviewRepository) { 6 | suspend operator fun invoke() = productsPreviewRepository.getProductsTopSales() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_black_36dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /data/src/main/java/com/narcissus/marketplace/data/model/CartItemBean.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.model 2 | 3 | data class CartItemBean( 4 | var productId: String? = null, 5 | var productImage: String? = null, 6 | var productPrice: Int = 0, 7 | var productName: String? = null, 8 | var amount: Int = 0, 9 | var isSelected: Boolean = false, 10 | var stock: Int? = null 11 | ) 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | import com.narcissus.marketplace.domain.model.orders.Order 4 | 5 | data class User( 6 | val id: String, 7 | val email: String, 8 | val firstName: String, 9 | val lastName: String, 10 | val cart: Cart, 11 | val orders: List, 12 | val cartNumber: Long, 13 | ) 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetPeopleAreBuyingProducts.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 4 | 5 | class GetPeopleAreBuyingProducts(private val productsPreviewRepository: ProductsPreviewRepository) { 6 | suspend operator fun invoke() = productsPreviewRepository.getProductsPeopleAreBuying() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/RemoveFromCart.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.repository.CartRepository 5 | 6 | class RemoveFromCart(private val cartRepository: CartRepository) { 7 | suspend operator fun invoke(cartItem: CartItem) = cartRepository.removeFromCart(cartItem) 8 | } 9 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_code.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/UserSideEffect.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user 2 | 3 | sealed class UserSideEffect { 4 | data class Toast(val text: String) : UserSideEffect() 5 | data class SwitchTheme(val checked: Boolean) : UserSideEffect() 6 | object NavigateToSignIn : UserSideEffect() 7 | object NavigateToOrders : UserSideEffect() 8 | object ViewSourceCode : UserSideEffect() 9 | } 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetSimilarProducts.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 4 | 5 | class GetSimilarProducts( 6 | private val productsPreviewRepository: ProductsPreviewRepository, 7 | ) { 8 | suspend operator fun invoke(id: String) = 9 | productsPreviewRepository.getSimilarProducts(id) 10 | } 11 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/ic_check_arrow.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/ProductPreview.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class ProductPreview( 4 | val id: String, 5 | val icon: String, 6 | val price: Int, 7 | val name: String, 8 | val department: String, 9 | val type: String, 10 | val stock: Int, 11 | val color: String, 12 | val material: String, 13 | val rating: Int, 14 | val sales: Int 15 | ) 16 | -------------------------------------------------------------------------------- /ui/cart/src/main/java/com/narcissus/marketplace/ui/cart/checkout/OrderConstants.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.cart.checkout 2 | 3 | object OrderConstants { 4 | const val PAY_INTENT_FILTER = "com.narcissus.marketplace.ui.cart.checkout.IntentFilter.PAY_INTENT_FILTER" 5 | const val KEY_ON_COMPLETE_NOTIFICATION_ID = "NotificationId" 6 | const val KEY_ORDER_UUID = "Order UUID" 7 | const val CHECKOUT_CHANNEL_ID = "MarketplaceChannelId" 8 | } 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/card/CardValidationResult.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.card 2 | 3 | sealed class CardValidationResult { 4 | object InvalidCardNumber : CardValidationResult() 5 | object InvalidExpirationDate : CardValidationResult() 6 | object InvalidCardHolderName : CardValidationResult() 7 | object InvalidCvv : CardValidationResult() 8 | object Success : CardValidationResult() 9 | } 10 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/anim/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetProductsByDepartmentId.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 4 | 5 | class GetProductsByDepartmentId(private val productsPreviewRepository: ProductsPreviewRepository) { 6 | suspend operator fun invoke(departmentId: String) = 7 | productsPreviewRepository.getProductsByDepartment(departmentId) 8 | } 9 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/anim/slide_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/anim/slide_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/anim/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/button_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/RestoreCartItems.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.repository.CartRepository 5 | 6 | class RestoreCartItems(private val cartRepository: CartRepository) { 7 | suspend operator fun invoke(orderList: List) = 8 | cartRepository.addAllSelectedToCart(orderList) 9 | } 10 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/di/UserModule.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user.di 2 | 3 | import com.narcissus.marketplace.ui.user.UserViewModel 4 | import com.narcissus.marketplace.ui.user.orders.OrdersViewModel 5 | import org.koin.androidx.viewmodel.dsl.viewModel 6 | import org.koin.dsl.module 7 | 8 | val userModule = module { 9 | viewModel { UserViewModel(get(), get()) } 10 | viewModel { OrdersViewModel(get()) } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/checkbox_unchecked.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/FragmentExt.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation 2 | 3 | import androidx.fragment.app.Fragment 4 | 5 | val Fragment.navigator: MarketplaceNavigator 6 | get() { 7 | return (requireActivity() as? MarketplaceNavigator) 8 | ?: throw IllegalStateException( 9 | "Host activity does not implement `MarketplaceCrossModuleNavigator` interface", 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/SetCartItemAmount.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.repository.CartRepository 5 | 6 | class SetCartItemAmount(private val cartRepository: CartRepository) { 7 | suspend operator fun invoke(cartItem: CartItem, amount: Int) = 8 | cartRepository.setCartItemAmount(cartItem, amount) 9 | } 10 | -------------------------------------------------------------------------------- /ui/product_details/src/main/java/com/narcissus/marketplace/ui/product_details/di/ProductDetailsModule.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.product_details.di 2 | 3 | import com.narcissus.marketplace.ui.product_details.ProductDetailsViewModel 4 | import org.koin.androidx.viewmodel.dsl.viewModel 5 | import org.koin.dsl.module 6 | 7 | val productDetailsModule = module { 8 | viewModel { (productId: String) -> ProductDetailsViewModel(productId, get(), get(), get()) } 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/button_small_gradient_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetTopRatedProductsByDepartment.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 4 | 5 | class GetTopRatedProductsByDepartment(private val productsPreviewRepository: ProductsPreviewRepository) { 6 | suspend operator fun invoke(departmentId: String) = 7 | productsPreviewRepository.getProductsByDepartmentIdTopRated(departmentId) 8 | } 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetTopSalesProductsByDepartment.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 4 | 5 | class GetTopSalesProductsByDepartment(private val productsPreviewRepository: ProductsPreviewRepository) { 6 | suspend operator fun invoke(departmentId: String) = 7 | productsPreviewRepository.getProductsByDepartmentIdTopSales(departmentId) 8 | } 9 | -------------------------------------------------------------------------------- /data/src/test/java/com/narcissus/marketplace/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/SetCartItemSelected.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.repository.CartRepository 5 | 6 | class SetCartItemSelected(private val cartRepository: CartRepository) { 7 | suspend operator fun invoke(cartItem: CartItem, selected: Boolean) = 8 | cartRepository.setCartItemSelected(cartItem, selected) 9 | } 10 | -------------------------------------------------------------------------------- /data/src/main/java/com/narcissus/marketplace/data/model/ProductPreviewBean.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.model 2 | 3 | data class ProductPreviewBean( 4 | var id: String = "", 5 | var icon: String = "", 6 | var price: Int = 0, 7 | var name: String = "", 8 | var department: String = "", 9 | var type: String = "", 10 | var stock: Int = 0, 11 | var color: String = "", 12 | var material: String = "", 13 | var rating: Int = 0, 14 | var sales: Int = 0 15 | ) 16 | -------------------------------------------------------------------------------- /ui/home/src/main/java/com/narcissus/marketplace/ui/home/recycler/ProductsOfTheDayAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.home.recycler 2 | 3 | import com.google.android.material.card.MaterialCardView 4 | import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter 5 | 6 | class ProductsOfTheDayAdapter( 7 | onProductClicked: (id: String, cardView: MaterialCardView) -> Unit 8 | ) : ListDelegationAdapter>( 9 | ProductOfTheDayItem.delegate(onProductClicked) 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/narcissus/marketplace/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.di 2 | 3 | import com.narcissus.marketplace.R 4 | import com.narcissus.marketplace.ui.sign_in.di.SignInQualifiers 5 | import org.koin.android.ext.koin.androidContext 6 | import org.koin.core.qualifier.qualifier 7 | import org.koin.dsl.module 8 | 9 | val appModule = module { 10 | factory(qualifier()) { 11 | androidContext().getString(R.string.default_web_client_id) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/auth/SignInResult.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.auth 2 | 3 | import com.narcissus.marketplace.domain.model.UserProfile 4 | 5 | sealed class SignInResult { 6 | object Error : SignInResult() 7 | object InvalidEmail : SignInResult() 8 | object BlankPassword : SignInResult() 9 | object WrongPassword : SignInResult() 10 | object UserNotFound : SignInResult() 11 | data class Success(val userProfile: UserProfile) : SignInResult() 12 | } 13 | -------------------------------------------------------------------------------- /ui/search/src/main/res/navigation/nav_graph_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/auth/SignUpResult.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.auth 2 | 3 | import com.narcissus.marketplace.domain.model.UserProfile 4 | 5 | sealed class SignUpResult { 6 | object Error : SignUpResult() 7 | object BlankFullName : SignUpResult() 8 | object InvalidEmail : SignUpResult() 9 | data class InvalidPassword(val failedRequirements: List) : SignUpResult() 10 | data class Success(val userProfile: UserProfile) : SignUpResult() 11 | } 12 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/CheckoutItem.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class CheckoutItem( 4 | val detailId: String, 5 | val detailName: String, 6 | val detailAmount: Int, 7 | val detailPrice: Int, 8 | ) 9 | 10 | fun CartItem.toCheckoutItem(): CheckoutItem = 11 | CheckoutItem( 12 | detailId = productId, 13 | detailName = productName, 14 | detailPrice = amount * productPrice, 15 | detailAmount = amount, 16 | ) 17 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_card.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_tune.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetCheckout.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.model.CheckoutItem 4 | import com.narcissus.marketplace.domain.model.toCheckoutItem 5 | import com.narcissus.marketplace.domain.repository.CartRepository 6 | 7 | class GetCheckout(private val cartRepository: CartRepository) { 8 | suspend operator fun invoke(): List = 9 | cartRepository.getSelectedCartItems().map { it.toCheckoutItem() } 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_sign_out.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /ui/cart/src/main/java/com/narcissus/marketplace/ui/cart/checkout/CheckoutScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.cart.checkout 2 | 3 | import com.narcissus.marketplace.domain.model.CheckoutItem 4 | 5 | sealed class CheckoutScreenState { 6 | object Loading : CheckoutScreenState() 7 | data class Idle(val items: List, val totalCost: Int) : CheckoutScreenState() 8 | data class PaymentFailed(val message: String) : CheckoutScreenState() 9 | data class PaymentSuccessful(val message: String) : CheckoutScreenState() 10 | } 11 | -------------------------------------------------------------------------------- /apiclient/src/main/java/com/narcissus/marketplace/apiclient/api/service/OrderApiService.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.apiclient.api.service 2 | 3 | import com.narcissus.marketplace.apiclient.api.model.OrderPaymentQueryBody 4 | import com.narcissus.marketplace.apiclient.api.model.OrderPaymentResponse 5 | import retrofit2.http.Body 6 | import retrofit2.http.POST 7 | 8 | interface OrderApiService { 9 | @POST("actions/checkout") 10 | suspend fun payForTheOrder( 11 | @Body body: OrderPaymentQueryBody 12 | ): OrderPaymentResponse 13 | } 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/orders/OrderItem.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model.orders 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | 5 | data class OrderItem( 6 | val productId: String, 7 | val productImage: String, 8 | val productPrice: Int, 9 | val productName: String, 10 | val amount: Int, 11 | val amountPrice: Int, 12 | ) 13 | fun CartItem.toOrderItem(): OrderItem = 14 | OrderItem(productId, productImage, productPrice, productName, amount, productPrice * amount) 15 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetCartItemsAmount.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.CartRepository 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.mapLatest 6 | 7 | class GetCartItemsAmount(private val cartRepository: CartRepository) { 8 | operator fun invoke(): Flow = 9 | cartRepository.getCart() 10 | .mapLatest { items -> 11 | items.count { it.isSelected } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/theme/Font.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user.theme 2 | 3 | import androidx.compose.ui.text.font.Font 4 | import androidx.compose.ui.text.font.toFontFamily 5 | import com.narcissus.marketplace.core.R as CORE 6 | 7 | val Montserrat = Font(CORE.font.montserrat_regular_ttf).toFontFamily() 8 | val MontserratMedium = Font(CORE.font.montserrat_medium_ttf).toFontFamily() 9 | val MontserratSemiBold = Font(CORE.font.montserrat_semibold_ttf).toFontFamily() 10 | val MontserratBold = Font(CORE.font.montserrat_bold_ttf).toFontFamily() 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_catalog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /persistence/src/main/java/com/narcissus/marketplace/data/persistence/database/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.persistence.database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.narcissus.marketplace.data.persistence.model.ProductEntity 6 | 7 | @Database( 8 | entities = [ProductEntity::class], 9 | version = 1, 10 | ) 11 | abstract class AppDatabase : RoomDatabase() { 12 | abstract fun productDao(): ProductDao 13 | 14 | companion object { 15 | const val DATABASE_NAME = "marketplace-app.db" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/repository/OrderRepository.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.repository 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.model.orders.Order 5 | import com.narcissus.marketplace.domain.model.orders.OrderPaymentResult 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface OrderRepository { 9 | fun getOrders(): Flow> 10 | suspend fun payForTheOrder(orderList: List, orderUUID: String): OrderPaymentResult 11 | suspend fun saveOrder(order: Order) 12 | } 13 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/layout/list_item_details_main_info_placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /ui/home/src/main/res/navigation/nav_graph_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ui/product_details/src/main/java/com/narcissus/marketplace/ui/product_details/ProductsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.product_details 2 | 3 | import com.google.android.material.card.MaterialCardView 4 | import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter 5 | 6 | class ProductsAdapter( 7 | onProductClicked: (id: String, cardView: MaterialCardView) -> Unit 8 | ) : AsyncListDifferDelegationAdapter( 9 | ProductListItem.DIFF_CALLBACK, 10 | ProductListItem.LoadingProduct.delegate(), 11 | ProductListItem.Product.delegate(onProductClicked), 12 | ) 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/util/FlowUtils.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.util 2 | 3 | import androidx.lifecycle.LifecycleCoroutineScope 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.collect 6 | 7 | fun Flow.launchWhenStarted(lifecycleScope: LifecycleCoroutineScope) { 8 | lifecycleScope.launchWhenStarted { 9 | this@launchWhenStarted.collect() 10 | } 11 | } 12 | 13 | fun Flow.launchWhenResumed(lifecycleScope: LifecycleCoroutineScope) { 14 | lifecycleScope.launchWhenResumed { 15 | this@launchWhenResumed.collect() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ui/catalog/src/main/res/navigation/nav_graph_catalog.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ui/splash/src/main/res/navigation/nav_graph_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/layout/list_item_details_product_placeholder.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/CartItem.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class CartItem( 4 | val productId: String, 5 | val productImage: String, 6 | val productPrice: Int, 7 | val productName: String, 8 | val amount: Int, 9 | val isSelected: Boolean, 10 | val stock: Int 11 | ) 12 | 13 | fun ProductPreview.toCartItem() = 14 | CartItem( 15 | productId = id, 16 | productImage = icon, 17 | productPrice = price, 18 | productName = name, 19 | amount = 1, 20 | isSelected = false, 21 | stock = stock 22 | ) 23 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_broom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_crescent.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /core/src/main/res/layout/list_item_recycler.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/narcissus/marketplace/core/navigation/MarketplaceNavigator.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.core.navigation 2 | 3 | import androidx.navigation.NavOptions 4 | import androidx.navigation.Navigator 5 | import com.narcissus.marketplace.core.navigation.destination.NavDestination 6 | 7 | interface MarketplaceNavigator { 8 | fun navigate(destination: NavDestination) 9 | fun navigate(destination: NavDestination, options: NavOptions) 10 | fun navigate(destination: NavDestination, extras: Navigator.Extras) 11 | fun navigate(destination: NavDestination, options: NavOptions, extras: Navigator.Extras) 12 | fun navigateUp() 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/res/layout/searchview_custom.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | -------------------------------------------------------------------------------- /data/src/main/java/com/narcissus/marketplace/data/DummyProducts.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data 2 | 3 | import com.narcissus.marketplace.domain.model.ProductPreview 4 | 5 | internal object DummyProducts { 6 | val previews = listOf( 7 | ProductPreview("1", "https://c.tenor.com/UjdeUF--bBkAAAAS/sussy.gif", 1449, "Apple MacBook Pro 13", "", "", 752, "", "", 3, 152), 8 | ProductPreview("2", "https://c.tenor.com/UjdeUF--bBkAAAAS/sussy.gif", 1299, "Apple MacBook Air 13", "", "", 1021, "", "", 5, 196), 9 | ProductPreview("3", "https://c.tenor.com/UjdeUF--bBkAAAAS/sussy.gif", 2199, "Apple MacBook Pro 16", "", "", 128, "", "", 4, 65), 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple200 = Color(0xFFBB86FC) 6 | val Purple500 = Color(0xFF6200EE) 7 | val Purple700 = Color(0xFF3700B3) 8 | val Teal200 = Color(0xFF03DAC5) 9 | 10 | val GradientBackgroundStart = Color(0xFF68B4FA) 11 | val GradientBackgroundEnd = Color(0xFF8E44EB) 12 | 13 | val Primary = Color(0xFF8E44EB) 14 | val Black = Color(0xFF000000) 15 | val White = Color(0xFFFFFFFF) 16 | val DarkPrimary = Color(0xFF292331) 17 | val GreyLight = Color(0xFFF6F6F6) 18 | val Grey = Color(0xFF707070) 19 | val Red = Color(0xFFF30808) 20 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/checkbox_checked.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ui/product_details/src/main/java/com/narcissus/marketplace/ui/product_details/utils/GetTextLinearGradient.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.product_details.utils 2 | 3 | import android.content.Context 4 | import android.graphics.LinearGradient 5 | import android.graphics.Shader 6 | import com.narcissus.marketplace.core.R as CORE 7 | 8 | fun getTextLinearGradient(context: Context) = LinearGradient( 9 | 0f, 10 | 0f, 11 | 100f, 12 | 100f, 13 | arrayOf( 14 | context.getColor(CORE.color.gradient_background_start), 15 | context.getColor(CORE.color.gradient_background_end), 16 | ).toIntArray(), 17 | null, 18 | Shader.TileMode.CLAMP, 19 | ) 20 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/layout/list_item_details_price.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/layout/list_item_product_preview_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /persistence/src/main/java/com/narcissus/marketplace/data/persistence/model/ProductEntity.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.persistence.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity(tableName = "product") 7 | data class ProductEntity( 8 | @PrimaryKey 9 | val id: String = "", 10 | val icon: String = "", 11 | val price: Int = 0, 12 | val name: String = "", 13 | val department: String = "", 14 | val type: String = "", 15 | val stock: Int = 0, 16 | val color: String = "", 17 | val material: String = "", 18 | val rating: Int = 0, 19 | val sales: Int = 0, 20 | val created: Long = System.currentTimeMillis(), 21 | ) 22 | -------------------------------------------------------------------------------- /ui/product_details/src/main/java/com/narcissus/marketplace/ui/product_details/model/ParcelableReview.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.product_details.model 2 | 3 | import android.os.Parcelable 4 | import com.narcissus.marketplace.domain.model.Review 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class ParcelableReview( 9 | val reviewId: String, 10 | val author: String, 11 | val details: String, 12 | val rating: Int, 13 | val reviewAuthorIcon: String, 14 | ) : Parcelable 15 | 16 | fun Review.toParcelableReview() = 17 | ParcelableReview( 18 | reviewId, 19 | author, 20 | details, 21 | rating, 22 | reviewAuthorIcon, 23 | ) 24 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/util/ActionResult.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.util 2 | 3 | typealias Mapper = (Input) -> Output 4 | 5 | sealed class ActionResult { 6 | class SuccessResult(val data: T) : ActionResult() 7 | class ErrorResult(val message: String) : ActionResult() 8 | 9 | fun mapResult(mapper: Mapper) = when (this) { 10 | is SuccessResult -> SuccessResult(mapper(this.data)) 11 | is ErrorResult -> ErrorResult(this.message) 12 | } 13 | 14 | fun getOrThrow(): T = when (this) { 15 | is ErrorResult -> error("Result is error") 16 | is SuccessResult -> this.data 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /persistence/src/main/java/com/narcissus/marketplace/data/persistence/database/ProductDao.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.persistence.database 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.narcissus.marketplace.data.persistence.model.ProductEntity 8 | import kotlinx.coroutines.flow.Flow 9 | 10 | @Dao 11 | interface ProductDao { 12 | // TODO: add limit of ~15 13 | @Query("SELECT * FROM product ORDER BY created DESC") 14 | fun getProducts(): Flow> 15 | 16 | @Insert(onConflict = OnConflictStrategy.REPLACE) 17 | suspend fun insertProduct(productEntity: ProductEntity) 18 | } 19 | -------------------------------------------------------------------------------- /ui/product_details/src/main/java/com/narcissus/marketplace/ui/product_details/main_info_recycler_view/ProductMainInfoAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.product_details.main_info_recycler_view 2 | 3 | import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter 4 | 5 | class ProductMainInfoAdapter( 6 | onPurchaseClicked: () -> Unit, 7 | onGoToCartClicked: () -> Unit, 8 | ) : AsyncListDifferDelegationAdapter( 9 | ProductMainInfoItem.DIFF_CALLBACK, 10 | ProductMainInfoItem.RatingSection.delegate(), 11 | ProductMainInfoItem.ActivePurchaseButton.delegate(onPurchaseClicked), 12 | ProductMainInfoItem.InactivePurchaseButton.delegate(onGoToCartClicked), 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/res/menu/bottom_navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /data/src/main/java/com/narcissus/marketplace/data/mapper/CartItemMapper.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.mapper 2 | 3 | import com.narcissus.marketplace.data.model.CartItemBean 4 | import com.narcissus.marketplace.domain.model.CartItem 5 | 6 | fun CartItemBean.toCartItem(): CartItem? = 7 | runCatching { 8 | CartItem( 9 | productId!!, 10 | productImage!!, 11 | productPrice, 12 | productName!!, 13 | amount, 14 | isSelected, 15 | stock!!, 16 | ) 17 | }.getOrDefault(null) 18 | 19 | fun CartItem.toBean(): CartItemBean = 20 | CartItemBean(productId, productImage, productPrice, productName, amount, isSelected, stock) 21 | -------------------------------------------------------------------------------- /ui/home/src/main/res/layout/fragment_home_screen_page.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/orders/OrderPaymentResult.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model.orders 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import java.util.Date 5 | 6 | class OrderPaymentResult( 7 | val id: String?, 8 | val number: Int?, 9 | val status: OrderPaymentStatus, 10 | val message: String, 11 | ) 12 | 13 | fun List.toOrder(orderUUID: String, orderNumber: Int, orderDate: Date, orderStatus: OrderStatus): Order = 14 | Order( 15 | orderUUID, 16 | orderNumber, 17 | orderDate, 18 | orderStatus, 19 | this.sumOf { it.amount * it.productPrice }, 20 | this.map { it.toOrderItem() }, 21 | ) 22 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/solid_stroke.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @dimen/margin_small 4 | 14sp 5 | 16sp 6 | 1dp 7 | 18dp 8 | 100dp 9 | 20dp 10 | 70dp 11 | 25dp 12 | 100dp 13 | 40dp 14 | 15 | -------------------------------------------------------------------------------- /ui/home/src/main/res/layout/list_item_headline.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/layout/list_item_details_titile_basic.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cart.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_cart.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /data/src/main/java/com/narcissus/marketplace/data/model/OrderBean.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.model 2 | 3 | import com.narcissus.marketplace.domain.model.orders.OrderStatus 4 | import java.util.Date 5 | 6 | data class OrderBean( 7 | val number: Int? = null, 8 | val id: String? = null, 9 | val date: Date? = null, 10 | val status: OrderStatus? = null, 11 | val summaryPrice: Int? = null, 12 | val items: List? = null, 13 | ) 14 | 15 | data class OrderItemBean( 16 | val productId: String? = null, 17 | val productImage: String? = null, 18 | val productPrice: Int? = null, 19 | val productName: String? = null, 20 | val amount: Int? = null, 21 | val amountPrice: Int? = null 22 | ) 23 | -------------------------------------------------------------------------------- /persistence/src/main/java/com/narcissus/marketplace/data/persistence/di/PersistenceModule.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.persistence.di 2 | 3 | import androidx.room.Room 4 | import com.narcissus.marketplace.data.persistence.database.AppDatabase 5 | import com.narcissus.marketplace.data.persistence.database.AppDatabase.Companion.DATABASE_NAME 6 | import org.koin.android.ext.koin.androidContext 7 | import org.koin.dsl.module 8 | 9 | val persistenceModule = module { 10 | single { 11 | Room.databaseBuilder( 12 | androidContext(), 13 | AppDatabase::class.java, 14 | DATABASE_NAME 15 | ).build() 16 | } 17 | 18 | single { 19 | get().productDao() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/ic_bug.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /apiclient/src/main/java/com/narcissus/marketplace/apiclient/api/model/DepartmentsResponse.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.apiclient.api.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class DepartmentsResponse( 6 | @SerializedName(SerializedNames.data) 7 | val data: List 8 | ) 9 | 10 | data class DepartmentResponseData( 11 | @SerializedName(SerializedNames.departmentId) 12 | val id: String, 13 | 14 | @SerializedName(SerializedNames.departmentName) 15 | val name: String, 16 | 17 | @SerializedName(SerializedNames.departmentNumProducts) 18 | val numberOfProducts: Int, 19 | 20 | @SerializedName(SerializedNames.departmentImageUrl) 21 | val imageUrl: String, 22 | ) 23 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "Marketplace App" 16 | include ':app' 17 | include ':core' 18 | include ':domain' 19 | include ':data' 20 | include ':ui:user' 21 | include ':apiclient' 22 | include ':persistence' 23 | include ':ui:search' 24 | include ':firebase' 25 | include ':ui:splash' 26 | include ':ui:cart' 27 | include ':ui:catalog' 28 | include ':ui:home' 29 | include ':ui:sign_in' 30 | include ':ui:product_details' 31 | -------------------------------------------------------------------------------- /.mega-linter.yml: -------------------------------------------------------------------------------- 1 | # Configuration file for MegaLinter 2 | # See all available variables at https://megalinter.github.io/configuration/ and in linters documentation 3 | 4 | APPLY_FIXES: all # all, none, or list of linter keys 5 | # ENABLE: # If you use ENABLE variable, all other languages/formats/tooling-formats will be disabled by default 6 | ENABLE_LINTERS: # If you use ENABLE_LINTERS variable, all other linters will be disabled by default 7 | - KOTLIN_KTLINT 8 | DISABLE: 9 | - COPYPASTE # Comment to enable checks of excessive copy-pastes 10 | # - SPELL # Uncomment to disable checks of spelling mistakes 11 | SHOW_ELAPSED_TIME: false 12 | FILEIO_REPORTER: true 13 | # DISABLE_ERRORS: true # Uncomment if you want MegaLinter to detect errors but not block CI to pass 14 | -------------------------------------------------------------------------------- /data/src/main/java/com/narcissus/marketplace/data/DepartmentRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data 2 | 3 | import com.narcissus.marketplace.apiclient.api.model.DepartmentResponseData 4 | import com.narcissus.marketplace.apiclient.api.service.ApiService 5 | import com.narcissus.marketplace.data.mapper.toDepartment 6 | import com.narcissus.marketplace.domain.model.Department 7 | import com.narcissus.marketplace.domain.repository.DepartmentRepository 8 | 9 | internal class DepartmentRepositoryImpl( 10 | private val apiService: ApiService, 11 | ) : DepartmentRepository { 12 | override suspend fun getDepartments(): List { 13 | return apiService.getDepartments().data 14 | .map(DepartmentResponseData::toDepartment) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ui/splash/src/main/java/com/narcissus/marketplace/ui/splash/SplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.splash 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.StateFlow 8 | import kotlinx.coroutines.flow.asStateFlow 9 | import kotlinx.coroutines.launch 10 | 11 | class SplashViewModel : ViewModel() { 12 | private val _isLaunchedFlow = MutableStateFlow(false) 13 | val isLaunchedFlow: StateFlow = _isLaunchedFlow.asStateFlow() 14 | 15 | fun launch() { 16 | viewModelScope.launch { 17 | delay(1000L) 18 | _isLaunchedFlow.value = true 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | NCS Shop 4 | Home 5 | Catalog 6 | Cart 7 | User 8 | NCS SHOP 9 | Order 10 | NCS Shop Logo 11 | $%1$d 12 | %1$dx 13 | #%1$s 14 | Please, enter your full name 15 | Add to cart 16 | 17 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/SignInWithEmail.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.auth.Patterns 4 | import com.narcissus.marketplace.domain.auth.SignInResult 5 | import com.narcissus.marketplace.domain.repository.UserRepository 6 | 7 | class SignInWithEmail(private val userRepository: UserRepository) { 8 | suspend operator fun invoke(email: String, pass: String): SignInResult { 9 | if (!Patterns.EMAIL.toRegex().matches(email)) { 10 | return SignInResult.InvalidEmail 11 | } 12 | 13 | if (pass.isBlank()) { 14 | return SignInResult.BlankPassword 15 | } 16 | 17 | return userRepository.signInWithEmail(email, pass) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ui/home/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Top sales 4 | Top rated 5 | You viewed 6 | Explore 7 | Special offer 8 | Products of the day 9 | -%1$d%% 10 | Featured 11 | That\'s it! 🙃\nVisit our catalog\nfor more cool offers 12 | Special offer banner 13 | People are buying 14 | 15 | -------------------------------------------------------------------------------- /ui/search/src/main/res/layout/fragment_search_history.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /apiclient/src/main/java/com/narcissus/marketplace/apiclient/api/interceptor/CacheInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.apiclient.api.interceptor 2 | 3 | import com.narcissus.marketplace.apiclient.Constants 4 | import okhttp3.CacheControl 5 | import okhttp3.Interceptor 6 | import okhttp3.Response 7 | import java.util.concurrent.TimeUnit 8 | 9 | class CacheInterceptor : Interceptor { 10 | override fun intercept(chain: Interceptor.Chain): Response { 11 | val response = chain.proceed(chain.request()) 12 | 13 | val cacheControl = CacheControl.Builder() 14 | .maxAge(Constants.CACHE_MAX_AGE, TimeUnit.MINUTES) 15 | .build() 16 | 17 | return response.newBuilder() 18 | .header("Cache-Control", cacheControl.toString()) 19 | .build() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ui/search/src/main/java/com/narcissus/marketplace/ui/search/SearchFragment.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.search 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | import com.narcissus.marketplace.ui.search.databinding.FragmentSearchHistoryBinding 7 | 8 | class SearchFragment : Fragment(R.layout.fragment_search_history) { 9 | private var _binding: FragmentSearchHistoryBinding? = null 10 | private val binding get() = _binding!! 11 | 12 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 13 | super.onViewCreated(view, savedInstanceState) 14 | _binding = FragmentSearchHistoryBinding.bind(view) 15 | } 16 | 17 | override fun onDestroyView() { 18 | super.onDestroyView() 19 | _binding = null 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/repository/CartRepository.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.repository 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface CartRepository { 7 | fun getCart(): Flow> 8 | suspend fun addToCart(cartItem: CartItem) 9 | suspend fun removeFromCart(cartItem: CartItem) 10 | suspend fun setCartItemSelected(cartItem: CartItem, selected: Boolean) 11 | suspend fun setCartItemAmount(cartItem: CartItem, amount: Int) 12 | suspend fun selectAllCartItems(isSelected: Boolean) 13 | suspend fun deleteSelectedItems() 14 | suspend fun addAllSelectedToCart(cartItems: List) 15 | suspend fun getSelectedCartItems(): List 16 | suspend fun getCartCost(): Int 17 | } 18 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetCartCostFlow.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.CartRepository 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.mapLatest 6 | 7 | class GetCartCostFlow(private val cartRepository: CartRepository) { 8 | operator fun invoke(): Flow = 9 | cartRepository.getCart() 10 | .mapLatest { items -> 11 | if (items.isEmpty()) { 12 | 0 13 | } else { 14 | items.asSequence() 15 | .filter { it.isSelected } 16 | .map { it.productPrice * it.amount } 17 | .reduceOrNull(Int::plus) ?: 0 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/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 -------------------------------------------------------------------------------- /data/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 -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/AddToCart.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.model.ProductDetails 4 | import com.narcissus.marketplace.domain.model.ProductPreview 5 | import com.narcissus.marketplace.domain.model.toCartItem 6 | import com.narcissus.marketplace.domain.model.toProductPreview 7 | import com.narcissus.marketplace.domain.repository.CartRepository 8 | 9 | class AddToCart(private val cartRepository: CartRepository) { 10 | suspend operator fun invoke(productPreview: ProductPreview) { 11 | val cartItem = productPreview.toCartItem() 12 | cartRepository.addToCart(cartItem) 13 | } 14 | 15 | suspend operator fun invoke(productDetails: ProductDetails) { 16 | invoke(productDetails.toProductPreview()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /firebase/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 -------------------------------------------------------------------------------- /ui/cart/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 -------------------------------------------------------------------------------- /ui/home/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 -------------------------------------------------------------------------------- /ui/product_details/src/main/res/layout/list_item_details_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /ui/user/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 -------------------------------------------------------------------------------- /apiclient/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 -------------------------------------------------------------------------------- /data/src/androidTest/java/com/narcissus/marketplace/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.narcissus.marketplace.test", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/model/ProductDetails.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.model 2 | 3 | data class ProductDetails( 4 | val id: String, 5 | val icon: String, 6 | val price: Int, 7 | val name: String, 8 | val department: String, 9 | val type: String, 10 | val stock: Int, 11 | val color: String, 12 | val material: String, 13 | val description: String, 14 | val rating: Int, 15 | val sales: Int, 16 | val reviews: List, 17 | ) 18 | 19 | fun ProductDetails.toProductPreview(): ProductPreview { 20 | return ProductPreview( 21 | id, 22 | icon, 23 | price, 24 | name, 25 | department, 26 | type, 27 | stock, 28 | color, 29 | material, 30 | rating, 31 | sales 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /persistence/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 -------------------------------------------------------------------------------- /ui/catalog/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 -------------------------------------------------------------------------------- /ui/search/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 -------------------------------------------------------------------------------- /ui/sign_in/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 -------------------------------------------------------------------------------- /ui/splash/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 -------------------------------------------------------------------------------- /ui/product_details/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 -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/util/SearchParams.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.util 2 | 3 | data class SearchParams( 4 | val query: String, 5 | val filters: Set = setOf(FilterType.None), 6 | val sortType: SortType = SortType.NONE, 7 | val sortDirection: SortDirection = SortDirection.ASC 8 | ) { 9 | 10 | sealed class FilterType { 11 | class Rating(val minValue: Int, maxValue: Int) : FilterType() 12 | class Sales(val minValue: Int, maxValue: Int) : FilterType() 13 | class Price(val minValue: Int, maxValue: Int) : FilterType() 14 | object None : FilterType() 15 | } 16 | 17 | enum class SortType { 18 | RATING, 19 | SALES, 20 | PRICE, 21 | NONE 22 | } 23 | 24 | enum class SortDirection { 25 | DESC, 26 | ASC 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/RemoveSelectedCartItems.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.repository.CartRepository 4 | import com.narcissus.marketplace.domain.usecase.RemoveSelectedCartItems 5 | import io.mockk.coEvery 6 | import io.mockk.coVerify 7 | import io.mockk.mockk 8 | import kotlinx.coroutines.runBlocking 9 | import org.junit.Test 10 | 11 | class RemoveSelectedCartItems { 12 | private val cartRepository = mockk { 13 | coEvery { deleteSelectedItems() } returns Unit 14 | } 15 | val removeSelectedFromCart = RemoveSelectedCartItems(cartRepository) 16 | 17 | @Test 18 | fun `should call removal of selected items`() { 19 | runBlocking { 20 | removeSelectedFromCart() 21 | } 22 | coVerify(exactly = 1) { removeSelectedFromCart() } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ui/user/src/main/res/navigation/nav_graph_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ui/home/src/main/res/animator/decrease_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /ui/home/src/main/res/animator/increase_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/GetProductDetails.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.model.ProductDetails 4 | import com.narcissus.marketplace.domain.model.toProductPreview 5 | import com.narcissus.marketplace.domain.repository.ProductsDetailsRepository 6 | import com.narcissus.marketplace.domain.repository.UserRepository 7 | 8 | class GetProductDetails( 9 | private val productsDetailsRepository: ProductsDetailsRepository, 10 | private val userRepository: UserRepository, 11 | ) { 12 | suspend operator fun invoke(productId: String): ProductDetails { 13 | val productDetails = productsDetailsRepository 14 | .getProductDetailsById(productId) 15 | 16 | userRepository.writeToVisitedProducts(productDetails.toProductPreview()) 17 | 18 | return productDetails 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apiclient/src/main/java/com/narcissus/marketplace/apiclient/api/model/OrderData.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.apiclient.api.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class OrderPaymentResponse( 6 | @SerializedName("order_id") 7 | val orderId: String, 8 | @SerializedName("order_number") 9 | val orderNumber: Int?, 10 | @SerializedName("order_payment_status") 11 | val orderPaymentStatus: String, 12 | @SerializedName("message") 13 | val message: String, 14 | ) 15 | 16 | data class OrderPaymentQueryBody( 17 | @SerializedName("id") 18 | val id: String, 19 | @SerializedName("order_items") 20 | val orderedItemsList: List, 21 | ) 22 | 23 | data class OrderPaymentRequestBodyItem( 24 | @SerializedName("product_id") 25 | val productId: String, 26 | @SerializedName("product_amount") 27 | val productAmount: Int, 28 | ) 29 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Product image 4 | %1$d sales 5 | In stock: %1$d 6 | Purchase 7 | Type 8 | Color 9 | Material 10 | Description 11 | About 12 | Reviews 13 | Similar Products 14 | Show all reviews 15 | Hide all reviews 16 | All reviews 17 | Go to Cart 18 | 19 | -------------------------------------------------------------------------------- /core/src/main/res/drawable/button_gradient_inactive.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/navigation/nav_graph_cart.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /core/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #707070 11 | #6B6B6B 12 | #EAE4E4 13 | #9432C2 14 | #EBEFFF 15 | #68B4FA 16 | #8E44EB 17 | #FF0000 18 | #121212 19 | #3E5ABE 20 | 21 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/RemoveFromCartTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.repository.CartRepository 5 | import com.narcissus.marketplace.domain.usecase.RemoveFromCart 6 | import io.mockk.coEvery 7 | import io.mockk.coVerify 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.runBlocking 10 | import org.junit.Test 11 | 12 | class RemoveFromCartTest { 13 | private val cartItem = mockk() 14 | private val cartRepository = mockk { 15 | coEvery { removeFromCart(cartItem) } returns Unit 16 | } 17 | val removeFromCart = RemoveFromCart(cartRepository) 18 | 19 | @Test 20 | fun `should remove item from cart`() { 21 | runBlocking { 22 | removeFromCart(cartItem) 23 | } 24 | coVerify(exactly = 1) { cartRepository.removeFromCart(cartItem) } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ui/home/src/main/res/layout/list_item_banner.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/SelectAllCartItemsTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.repository.CartRepository 4 | import com.narcissus.marketplace.domain.usecase.SelectAllCartItems 5 | import io.mockk.coEvery 6 | import io.mockk.coVerify 7 | import io.mockk.mockk 8 | import kotlinx.coroutines.runBlocking 9 | import org.junit.Test 10 | 11 | class SelectAllCartItemsTest { 12 | private val isSelected = mockk(relaxed = true) 13 | private val cartRepository = mockk { 14 | coEvery { selectAllCartItems(any()) } returns Unit 15 | } 16 | private val selectAllCartItems = SelectAllCartItems(cartRepository) 17 | 18 | @Test 19 | fun `should call all cart item selection with correct attribute`() { 20 | runBlocking { 21 | selectAllCartItems(isSelected) 22 | } 23 | coVerify(exactly = 1) { cartRepository.selectAllCartItems(isSelected) } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/res/layout/layout_progress_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 21 | 22 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/GetCartTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.repository.CartRepository 5 | import com.narcissus.marketplace.domain.usecase.GetCart 6 | import io.mockk.coEvery 7 | import io.mockk.coVerify 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.runBlocking 11 | import org.junit.Assert 12 | import org.junit.Test 13 | 14 | class GetCartTest { 15 | private val exceptedResult = mockk>>() 16 | private val cartRepository = mockk { 17 | coEvery { getCart() } returns exceptedResult 18 | } 19 | private val getCart = GetCart(cartRepository) 20 | private val result = getCart() 21 | 22 | @Test 23 | fun `should return actual cart`() { 24 | runBlocking { 25 | Assert.assertEquals(exceptedResult, result) 26 | } 27 | coVerify(exactly = 1) { cartRepository.getCart() } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ui/home/src/main/res/layout/list_item_featured_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/SetCartItemAmountTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.repository.CartRepository 5 | import com.narcissus.marketplace.domain.usecase.SetCartItemAmount 6 | import io.mockk.coEvery 7 | import io.mockk.coVerify 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.runBlocking 10 | import org.junit.Test 11 | 12 | class SetCartItemAmountTest { 13 | private val expectedCartItem = mockk() 14 | private val amount = mockk(relaxed = true) 15 | private val cartRepository = mockk { 16 | coEvery { setCartItemAmount(expectedCartItem, amount) } returns Unit 17 | } 18 | private val setCartItemAmount = SetCartItemAmount(cartRepository) 19 | 20 | @Test 21 | fun `should set cart item amount`() { 22 | runBlocking { 23 | setCartItemAmount(expectedCartItem, amount) 24 | } 25 | coVerify(exactly = 1) { cartRepository.setCartItemAmount(expectedCartItem, amount) } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ui/sign_in/src/main/res/navigation/nav_graph_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/SignUpWithEmail.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.auth.PasswordRequirement 4 | import com.narcissus.marketplace.domain.auth.Patterns.EMAIL 5 | import com.narcissus.marketplace.domain.auth.SignUpResult 6 | import com.narcissus.marketplace.domain.repository.UserRepository 7 | 8 | class SignUpWithEmail(private val userRepository: UserRepository) { 9 | suspend operator fun invoke(fullName: String, email: String, password: String): SignUpResult { 10 | if (fullName.isBlank()) { 11 | return SignUpResult.BlankFullName 12 | } 13 | 14 | if (email.isBlank() || !EMAIL.toRegex().matches(email)) { 15 | return SignUpResult.InvalidEmail 16 | } 17 | 18 | val failedRequirements = PasswordRequirement.findFailedRequirements(password) 19 | if (failedRequirements.isNotEmpty()) { 20 | return SignUpResult.InvalidPassword(failedRequirements) 21 | } 22 | 23 | return userRepository.signUpWithEmail(fullName, email, password) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/SetCartItemSelectedTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.repository.CartRepository 5 | import com.narcissus.marketplace.domain.usecase.SetCartItemSelected 6 | import io.mockk.coEvery 7 | import io.mockk.coVerify 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.runBlocking 10 | import org.junit.Test 11 | 12 | class SetCartItemSelectedTest { 13 | private val expectedCartItem = mockk() 14 | private val selection = mockk(relaxed = true) 15 | private val cartRepository = mockk { 16 | coEvery { setCartItemSelected(expectedCartItem, selection) } returns Unit 17 | } 18 | private val setCartItemAmount = SetCartItemSelected(cartRepository) 19 | 20 | @Test 21 | fun `should set cart item selection`() { 22 | runBlocking { 23 | setCartItemAmount(expectedCartItem, selection) 24 | } 25 | coVerify(exactly = 1) { cartRepository.setCartItemSelected(expectedCartItem, selection) } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/GetRandomProductsTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.model.ProductPreview 4 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 5 | import com.narcissus.marketplace.domain.usecase.GetRandomProducts 6 | import io.mockk.coEvery 7 | import io.mockk.coVerify 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.runBlocking 10 | import org.junit.Assert 11 | import org.junit.Test 12 | 13 | class GetRandomProductsTest { 14 | private val expectedProductPreviews = mockk>() 15 | private val productsPreviewRepository = mockk { 16 | coEvery { getProductsRandom() } returns expectedProductPreviews 17 | } 18 | private val getRandomProducts = GetRandomProducts(productsPreviewRepository) 19 | 20 | @Test 21 | fun `should return actual random products`() { 22 | runBlocking { 23 | Assert.assertEquals(expectedProductPreviews, getRandomProducts()) 24 | } 25 | coVerify(exactly = 1) { productsPreviewRepository.getProductsRandom() } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /data/src/main/java/com/narcissus/marketplace/data/mapper/ProductPreviewMapper.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.mapper 2 | 3 | import com.narcissus.marketplace.data.model.ProductPreviewBean 4 | import com.narcissus.marketplace.data.persistence.model.ProductEntity 5 | import com.narcissus.marketplace.domain.model.ProductPreview 6 | 7 | fun ProductEntity.toProductPreview(): ProductPreview { 8 | return ProductPreview( 9 | id = id, 10 | icon = icon, 11 | price = price, 12 | name = name, 13 | department = department, 14 | type = type, 15 | stock = stock, 16 | color = color, 17 | material = material, 18 | rating = rating, 19 | sales = sales 20 | ) 21 | } 22 | 23 | fun ProductPreviewBean.toProductPreview(): ProductPreview { 24 | return ProductPreview( 25 | id = id, 26 | icon = icon, 27 | price = price, 28 | name = name, 29 | department = department, 30 | type = type, 31 | stock = stock, 32 | color = color, 33 | material = material, 34 | rating = rating, 35 | sales = sales, 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/GetTopRatedProductsTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.model.ProductPreview 4 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 5 | import com.narcissus.marketplace.domain.usecase.GetTopRatedProducts 6 | import io.mockk.coEvery 7 | import io.mockk.coVerify 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.runBlocking 10 | import org.junit.Assert 11 | import org.junit.Test 12 | 13 | class GetTopRatedProductsTest { 14 | private val expectedProductPreviews = mockk>() 15 | private val productsPreviewRepository = mockk { 16 | coEvery { getProductsTopRated() } returns expectedProductPreviews 17 | } 18 | private val getTopRatedProducts = GetTopRatedProducts(productsPreviewRepository) 19 | 20 | @Test 21 | fun `should return actual top rated products`() { 22 | runBlocking { 23 | Assert.assertEquals(expectedProductPreviews, getTopRatedProducts()) 24 | } 25 | coVerify(exactly = 1) { productsPreviewRepository.getProductsTopRated() } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/GetTopSalesProductsTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.model.ProductPreview 4 | import com.narcissus.marketplace.domain.repository.ProductsPreviewRepository 5 | import com.narcissus.marketplace.domain.usecase.GetTopSalesProducts 6 | import io.mockk.coEvery 7 | import io.mockk.coVerify 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.runBlocking 10 | import org.junit.Assert 11 | import org.junit.Test 12 | 13 | class GetTopSalesProductsTest { 14 | private val expectedProductPreviews = mockk>() 15 | private val productsPreviewRepository = mockk { 16 | coEvery { getProductsTopSales() } returns expectedProductPreviews 17 | } 18 | private val getTopSalesProducts = GetTopSalesProducts(productsPreviewRepository) 19 | 20 | @Test 21 | fun `should return actual top rated products`() { 22 | runBlocking { 23 | Assert.assertEquals(expectedProductPreviews, getTopSalesProducts()) 24 | } 25 | coVerify(exactly = 1) { productsPreviewRepository.getProductsTopSales() } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/ic_visa.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/product_details/src/main/java/com/narcissus/marketplace/ui/product_details/reviews/DividerDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.product_details.reviews 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.drawable.Drawable 5 | import androidx.recyclerview.widget.RecyclerView 6 | import androidx.recyclerview.widget.RecyclerView.ItemDecoration 7 | 8 | class DividerDecoration( 9 | private val divider: Drawable, 10 | ) : ItemDecoration() { 11 | override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { 12 | val dividerLeft = parent.paddingLeft 13 | val dividerRight = parent.width - parent.paddingRight 14 | val childCount = parent.childCount 15 | for (i in 0..childCount - 2) { 16 | val child = parent.getChildAt(i) 17 | val params = child.layoutParams as RecyclerView.LayoutParams 18 | val dividerTop = child.bottom + params.bottomMargin 19 | val dividerBottom = dividerTop + divider.intrinsicHeight 20 | divider.setBounds(dividerLeft, dividerTop, dividerRight, dividerBottom) 21 | divider.draw(canvas) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apiclient/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' 5 | } 6 | 7 | android { 8 | compileSdk 32 9 | 10 | defaultConfig { 11 | minSdk 24 12 | targetSdk 32 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles "consumer-rules.pro" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | kotlinOptions { 29 | jvmTarget = '1.8' 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation(Retrofit.retrofit) 35 | implementation(Retrofit.gsonConverters) 36 | 37 | implementation(Coroutines.coroutinesCore) 38 | 39 | implementation(Koin.koinCore) 40 | implementation(Koin.koinAndroid) 41 | testImplementation(Koin.koinTest) 42 | } 43 | -------------------------------------------------------------------------------- /ui/catalog/src/main/java/com/narcissus/marketplace/ui/catalog/CatalogViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.catalog 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.narcissus.marketplace.domain.usecase.GetDepartments 6 | import com.narcissus.marketplace.ui.catalog.DepartmentListItem.DepartmentItem 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.SharingStarted 9 | import kotlinx.coroutines.flow.flow 10 | import kotlinx.coroutines.flow.shareIn 11 | 12 | class CatalogViewModel( 13 | private val getDepartments: GetDepartments, 14 | ) : ViewModel() { 15 | companion object { 16 | private const val DEPARTMENTS_AMOUNT = 14 17 | } 18 | 19 | val departments: Flow> = flow { 20 | val dummyDepartments = Array(DEPARTMENTS_AMOUNT) { 21 | DepartmentListItem.LoadingDepartmentItem() 22 | } 23 | emit(dummyDepartments.toList()) 24 | 25 | val departments = getDepartments().map(::DepartmentItem) 26 | emit(departments) 27 | }.shareIn( 28 | viewModelScope, 29 | SharingStarted.Lazily, 30 | 1 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /ui/sign_in/src/main/java/com/narcissus/marketplace/ui/sign_in/sign_up/SignUpViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.sign_in.sign_up 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.narcissus.marketplace.domain.auth.SignUpResult 6 | import com.narcissus.marketplace.domain.usecase.SignUpWithEmail 7 | import kotlinx.coroutines.channels.BufferOverflow 8 | import kotlinx.coroutines.flow.MutableSharedFlow 9 | import kotlinx.coroutines.flow.asSharedFlow 10 | import kotlinx.coroutines.launch 11 | 12 | class SignUpViewModel( 13 | val signUpWithEmail: SignUpWithEmail, 14 | ) : ViewModel() { 15 | private val _signUpResultFlow = MutableSharedFlow( 16 | replay = 1, 17 | extraBufferCapacity = 1, 18 | onBufferOverflow = BufferOverflow.DROP_OLDEST 19 | ) 20 | 21 | val signUpResultFlow = _signUpResultFlow.asSharedFlow() 22 | 23 | fun signUpWithEmailPassword(fullName: String, email: String, password: String) { 24 | viewModelScope.launch { 25 | val authResult = signUpWithEmail(fullName, email, password) 26 | _signUpResultFlow.emit(authResult) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apiclient/src/main/java/com/narcissus/marketplace/apiclient/api/model/ProductPreviewsResponse.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.apiclient.api.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class ProductPreviewsResponse( 6 | @SerializedName(SerializedNames.data) 7 | val data: List 8 | ) 9 | 10 | data class ProductPreviewResponseData( 11 | @SerializedName(SerializedNames.id) 12 | val id: String, 13 | @SerializedName(SerializedNames.icon) 14 | val icon: String, 15 | @SerializedName(SerializedNames.productName) 16 | val name: String, 17 | @SerializedName(SerializedNames.price) 18 | val price: Int, 19 | @SerializedName(SerializedNames.type) 20 | val type: String, 21 | @SerializedName(SerializedNames.productDepartment) 22 | val departmentName: String, 23 | @SerializedName(SerializedNames.stock) 24 | val stock: Int, 25 | @SerializedName(SerializedNames.color) 26 | val color: String, 27 | @SerializedName(SerializedNames.material) 28 | val material: String, 29 | @SerializedName(SerializedNames.productRating) 30 | val rating: Int, 31 | @SerializedName(SerializedNames.sales) 32 | val sales: Int 33 | ) 34 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.repository 2 | 3 | import com.narcissus.marketplace.domain.auth.AuthState 4 | import com.narcissus.marketplace.domain.auth.SignInResult 5 | import com.narcissus.marketplace.domain.auth.SignOutResult 6 | import com.narcissus.marketplace.domain.auth.SignUpResult 7 | import com.narcissus.marketplace.domain.model.ProductPreview 8 | import com.narcissus.marketplace.domain.model.User 9 | import kotlinx.coroutines.flow.Flow 10 | 11 | interface UserRepository { 12 | suspend fun addCard(cardNumber: Long, svv: Int, expirationDate: String) 13 | 14 | suspend fun getUserData(): User 15 | suspend fun isUserAuthenticated(): Boolean 16 | suspend fun signInWithEmail(email: String, password: String): SignInResult 17 | suspend fun signUpWithEmail(fullName: String, email: String, password: String): SignUpResult 18 | suspend fun signOut(): SignOutResult 19 | suspend fun signInWithGoogle(idToken: String): SignInResult 20 | 21 | val authStateFlow: Flow 22 | 23 | val recentlyVisitedProducts: Flow> 24 | suspend fun writeToVisitedProducts(productPreview: ProductPreview) 25 | } 26 | -------------------------------------------------------------------------------- /ui/cart/src/main/res/drawable/ic_mastercard.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/repository/ProductsPreviewRepository.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.repository 2 | 3 | import com.narcissus.marketplace.domain.model.ProductPreview 4 | import com.narcissus.marketplace.domain.util.ActionResult 5 | import com.narcissus.marketplace.domain.util.SearchParams 6 | 7 | interface ProductsPreviewRepository { 8 | suspend fun searchProducts( 9 | query: String, 10 | filters: Set 11 | ): ActionResult> 12 | 13 | suspend fun getProducts(): List 14 | suspend fun getProductsRandom(): List 15 | suspend fun getProductsTopRated(): List 16 | suspend fun getProductsTopSales(): List 17 | suspend fun getProductsPeopleAreBuying(): List 18 | suspend fun getProductsByDepartment(departmentId: String): ActionResult> 19 | suspend fun getProductsByDepartmentIdTopRated(departmentId: String): ActionResult> 20 | suspend fun getProductsByDepartmentIdTopSales(departmentId: String): ActionResult> 21 | suspend fun getSimilarProducts(productId: String): List 22 | } 23 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/AddCard.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.repository.UserRepository 4 | 5 | class AddCard( 6 | private val userRepository: UserRepository, 7 | ) { 8 | suspend operator fun invoke( 9 | cardNumber: Long, 10 | svv: Int, 11 | expirationDate: String 12 | ): Boolean { 13 | return if (checkIfCardNumberIsValid(cardNumber)) { 14 | userRepository.addCard(cardNumber, svv, expirationDate) 15 | true 16 | } else false 17 | } 18 | 19 | private fun checkIfCardNumberIsValid(cardNumber: Long): Boolean { 20 | val cardNumberStr = cardNumber.toString() 21 | var sum: Int = Character.getNumericValue(cardNumberStr[cardNumberStr.length - 1]) 22 | val parity: Int = cardNumberStr.length % 2 23 | for (i in cardNumberStr.length - 2 downTo 0) { 24 | var summand: Int = Character.getNumericValue(cardNumberStr[i]) 25 | if (i % 2 == parity) { 26 | val product = summand * 2 27 | summand = if (product > 9) product - 9 else product 28 | } 29 | sum += summand 30 | } 31 | return (sum % 10 == 0) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/GetRecentlyVisitedProductsTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.model.ProductPreview 4 | import com.narcissus.marketplace.domain.repository.UserRepository 5 | import com.narcissus.marketplace.domain.usecase.GetRecentlyVisitedProducts 6 | import io.mockk.coEvery 7 | import io.mockk.coVerify 8 | import io.mockk.mockk 9 | import kotlinx.coroutines.flow.flowOf 10 | import kotlinx.coroutines.runBlocking 11 | import org.junit.Assert 12 | import org.junit.Test 13 | 14 | class GetRecentlyVisitedProductsTest { 15 | private val expectedRecentlyVisitedProductsPreviews = flowOf(mockk>()) 16 | private val userRepository = mockk { 17 | coEvery { recentlyVisitedProducts } returns expectedRecentlyVisitedProductsPreviews 18 | } 19 | private val getRecentlyVisitedProducts = GetRecentlyVisitedProducts(userRepository) 20 | 21 | @Test 22 | fun `should return actual recently visited products`() { 23 | runBlocking { 24 | Assert.assertEquals( 25 | expectedRecentlyVisitedProductsPreviews, 26 | getRecentlyVisitedProducts(), 27 | ) 28 | } 29 | coVerify(exactly = 1) { userRepository.recentlyVisitedProducts } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apiclient/src/main/java/com/narcissus/marketplace/apiclient/api/model/SerializedNames.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.apiclient.api.model 2 | 3 | internal object SerializedNames { 4 | const val icon = "product_image_lg" 5 | const val id = "_id" 6 | const val productName = "product_name" 7 | const val price = "product_price" 8 | const val type = "product_type" 9 | const val productDepartment = "product_department" 10 | const val stock = "product_stock" 11 | const val color = "product_color" 12 | const val material = "product_material" 13 | const val productRating = "product_ratings" 14 | const val sales = "product_sales" 15 | const val data = "data" 16 | const val productDescription = "product_description" 17 | const val productReviews = "product_reviews" 18 | const val reviewProductId = "review_productid" 19 | const val reviewAuthor = "review_name" 20 | const val reviewDetails = "review_details" 21 | const val reviewRating = "review_rating" 22 | const val reviewAvatar = "review_avatar" 23 | const val similarProducts = "product_similar" 24 | const val departmentId = "department_id" 25 | const val departmentName = "department_name" 26 | const val departmentNumProducts = "department_numProducts" 27 | const val departmentImageUrl = "department_imageUrl" 28 | } 29 | -------------------------------------------------------------------------------- /firebase/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | minSdk 24 11 | targetSdk 32 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles "consumer-rules.pro" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | kotlinOptions { 28 | jvmTarget = '1.8' 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation(Koin.koinCore) 34 | implementation(Koin.koinAndroid) 35 | 36 | implementation(Firebase.database) 37 | implementation(Firebase.auth) 38 | implementation platform(Firebase.firebaseBom) 39 | 40 | 41 | implementation(Androidx.core) 42 | implementation(Androidx.appCompat) 43 | implementation(Google.material) 44 | testImplementation(Junit.junit) 45 | androidTestImplementation(Androidx.junit) 46 | androidTestImplementation(Androidx.espressoCore) 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /data/src/main/java/com/narcissus/marketplace/data/mapper/OrderMapper.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.mapper 2 | 3 | import com.narcissus.marketplace.data.OrderRepositoryImpl 4 | import com.narcissus.marketplace.data.model.OrderBean 5 | import com.narcissus.marketplace.data.model.OrderItemBean 6 | import com.narcissus.marketplace.domain.model.orders.Order 7 | import com.narcissus.marketplace.domain.model.orders.OrderItem 8 | import com.narcissus.marketplace.domain.model.orders.OrderPaymentStatus 9 | 10 | fun OrderBean.toOrder() = runCatching { 11 | Order( 12 | id!!, 13 | number!!, 14 | date!!, 15 | status!!, 16 | summaryPrice!!, 17 | items!!.map { it.toOrderItem().getOrThrow() }, 18 | ) 19 | }.getOrNull() 20 | 21 | fun OrderItemBean.toOrderItem() = 22 | runCatching { 23 | OrderItem( 24 | productId!!, 25 | productImage!!, 26 | productPrice!!, 27 | productName!!, 28 | amount!!, 29 | amountPrice!!, 30 | ) 31 | } 32 | 33 | fun orderPaymentResponseStatusToOrderPaymentStatus(paymentStatusResponse: String): OrderPaymentStatus { 34 | return when (paymentStatusResponse) { 35 | OrderRepositoryImpl.PAYMENT_STATUS_PAID -> OrderPaymentStatus.PAID 36 | else -> OrderPaymentStatus.CANCELLED 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/usecase/MakeAnOrder.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.usecase 2 | 3 | import com.narcissus.marketplace.domain.model.CartItem 4 | import com.narcissus.marketplace.domain.model.orders.OrderPaymentResult 5 | import com.narcissus.marketplace.domain.model.orders.OrderPaymentStatus 6 | import com.narcissus.marketplace.domain.model.orders.OrderStatus 7 | import com.narcissus.marketplace.domain.model.orders.toOrder 8 | import com.narcissus.marketplace.domain.repository.CartRepository 9 | import com.narcissus.marketplace.domain.repository.OrderRepository 10 | import java.util.Calendar 11 | 12 | class MakeAnOrder( 13 | private val orderRepository: OrderRepository, 14 | private val cartRepository: CartRepository, 15 | ) { 16 | suspend operator fun invoke(orderList: List, orderUUID: String): OrderPaymentResult { 17 | cartRepository.deleteSelectedItems() 18 | val paymentResult = orderRepository.payForTheOrder(orderList, orderUUID) 19 | if (paymentResult.status == OrderPaymentStatus.PAID) { 20 | orderRepository.saveOrder( 21 | orderList.toOrder( 22 | orderUUID, 23 | paymentResult.number!!, 24 | Calendar.getInstance().time, OrderStatus.ACCEPTED, 25 | ), 26 | ) 27 | } 28 | return paymentResult 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /persistence/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | } 6 | 7 | android { 8 | compileSdk 32 9 | 10 | defaultConfig { 11 | minSdk 24 12 | targetSdk 32 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles "consumer-rules.pro" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | kotlinOptions { 29 | jvmTarget = '1.8' 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation project(Modules.domain) 35 | 36 | implementation(Room.room) 37 | implementation(Room.roomRuntime) 38 | kapt(Room.roomCompiler) 39 | 40 | implementation(Koin.koinCore) 41 | implementation(Koin.koinAndroid) 42 | testImplementation(Koin.koinTest) 43 | 44 | implementation(Androidx.core) 45 | implementation(Androidx.appCompat) 46 | implementation(Google.material) 47 | testImplementation(Junit.junit) 48 | androidTestImplementation(Androidx.junit) 49 | androidTestImplementation(Androidx.espressoCore) 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/narcissus/marketplace/ui/MarketplaceApp.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui 2 | 3 | import android.app.Application 4 | import com.narcissus.marketplace.data.di.dataModule 5 | import com.narcissus.marketplace.di.appModule 6 | import com.narcissus.marketplace.di.domainModule 7 | import com.narcissus.marketplace.ui.cart.di.cartModule 8 | import com.narcissus.marketplace.ui.catalog.di.catalogModule 9 | import com.narcissus.marketplace.ui.home.di.homeModule 10 | import com.narcissus.marketplace.ui.product_details.di.productDetailsModule 11 | import com.narcissus.marketplace.ui.sign_in.di.signInModule 12 | import com.narcissus.marketplace.ui.user.di.userModule 13 | import org.koin.android.ext.koin.androidContext 14 | import org.koin.androidx.workmanager.koin.workManagerFactory 15 | import org.koin.core.context.startKoin 16 | 17 | class MarketplaceApp : Application() { 18 | override fun onCreate() { 19 | super.onCreate() 20 | startKoin { 21 | androidContext(this@MarketplaceApp) 22 | workManagerFactory() 23 | modules( 24 | appModule, 25 | dataModule, 26 | domainModule, 27 | homeModule, 28 | catalogModule, 29 | cartModule, 30 | userModule, 31 | signInModule, 32 | productDetailsModule, 33 | ) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | -------------------------------------------------------------------------------- /domain/src/test/java/com/narcissus/marketplace/AddToCartTest.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace 2 | 3 | import com.narcissus.marketplace.domain.model.ProductDetails 4 | import com.narcissus.marketplace.domain.model.toCartItem 5 | import com.narcissus.marketplace.domain.model.toProductPreview 6 | import com.narcissus.marketplace.domain.repository.CartRepository 7 | import com.narcissus.marketplace.domain.usecase.AddToCart 8 | import io.mockk.coEvery 9 | import io.mockk.coVerifySequence 10 | import io.mockk.mockk 11 | import kotlinx.coroutines.runBlocking 12 | import org.junit.Test 13 | 14 | class AddToCartTest { 15 | 16 | private val cartRepository = mockk { 17 | coEvery { addToCart(any()) } returns Unit 18 | } 19 | val addToCart = AddToCart(cartRepository) 20 | private val productDetails = mockk(relaxed = true) 21 | private val cartItemExpected = productDetails.toProductPreview().toCartItem() 22 | @Test 23 | fun `should add product preview to cart`() { 24 | runBlocking { addToCart(productDetails.toProductPreview()) } 25 | coVerifySequence { 26 | cartRepository.addToCart(cartItemExpected) 27 | } 28 | } 29 | 30 | @Test 31 | fun `should add product details to cart`() { 32 | 33 | runBlocking { addToCart(productDetails) } 34 | coVerifySequence { 35 | cartRepository.addToCart(cartItemExpected) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/layout/list_item_details_title_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 30 | 31 | -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | minSdk 24 11 | targetSdk 32 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles "consumer-rules.pro" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | kotlinOptions { 28 | jvmTarget = '1.8' 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation(Koin.koinCore) 34 | testImplementation(Koin.koinTest) 35 | 36 | implementation platform(Firebase.firebaseBom) 37 | implementation(Firebase.auth) 38 | implementation(Firebase.database) 39 | implementation(Google.playServicesAuth) 40 | 41 | implementation project(Modules.domain) 42 | implementation project(Modules.apiClient) 43 | implementation project(Modules.persistence) 44 | implementation project(Modules.firebase) 45 | 46 | implementation(Coroutines.coroutinesCore) 47 | implementation(Coroutines.coroutinesPlayServices) 48 | 49 | testImplementation(Junit.junit) 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 25 | 26 | -------------------------------------------------------------------------------- /firebase/src/main/java/com/narcissus/marketplace/data/firebase/di/FirebaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.data.firebase.di 2 | 3 | import android.util.Log 4 | import com.google.firebase.auth.FirebaseAuth 5 | import com.google.firebase.auth.ktx.auth 6 | import com.google.firebase.database.FirebaseDatabase 7 | import com.google.firebase.database.ktx.database 8 | import com.google.firebase.ktx.Firebase 9 | import com.narcissus.marketplace.data.firebase.Constants 10 | import org.koin.core.qualifier.qualifier 11 | import org.koin.dsl.module 12 | 13 | val firebaseModule = module { 14 | single { 15 | Firebase.database(Constants.DATABASE_URL) 16 | } 17 | 18 | single { 19 | Firebase.auth 20 | } 21 | 22 | factory( 23 | qualifier() 24 | ) { 25 | get().currentUser?.uid ?: "anonymous" 26 | } 27 | 28 | single( 29 | qualifier() 30 | ) { 31 | val userId = get(qualifier()) 32 | Log.d("marketplace-debug", "user id: $userId") 33 | get().getReference("${Constants.CHILD_CART}/$userId") 34 | } 35 | single( 36 | qualifier() 37 | ) { 38 | val userId = get(qualifier()) 39 | Log.d("marketplace-debug", "user id: $userId") 40 | get().getReference("${Constants.CHILD_ORDERS}/$userId") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/orders/OrdersViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user.orders 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.narcissus.marketplace.domain.model.orders.Order 6 | import com.narcissus.marketplace.domain.usecase.GetOrderList 7 | import kotlinx.coroutines.flow.launchIn 8 | import kotlinx.coroutines.flow.onEach 9 | import org.orbitmvi.orbit.Container 10 | import org.orbitmvi.orbit.ContainerHost 11 | import org.orbitmvi.orbit.syntax.simple.intent 12 | import org.orbitmvi.orbit.syntax.simple.postSideEffect 13 | import org.orbitmvi.orbit.syntax.simple.reduce 14 | import org.orbitmvi.orbit.viewmodel.container 15 | 16 | class OrdersViewModel( 17 | getOrderList: GetOrderList, 18 | ) : ViewModel(), ContainerHost { 19 | 20 | override val container: Container = 21 | container(OrdersState(isLoading = true)) 22 | 23 | init { 24 | getOrderList() 25 | .onEach(::updateScreenState) 26 | .launchIn(viewModelScope) 27 | } 28 | 29 | private fun updateScreenState(orders: List) = intent { 30 | reduce { 31 | OrdersState( 32 | isLoading = false, 33 | orders = orders, 34 | ) 35 | } 36 | } 37 | 38 | fun navigateUp() = intent { 39 | postSideEffect(OrdersSideEffect.NavigateUp) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ui/product_details/src/main/res/layout/list_item_details_product_about_multiple_lines.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 20 | 21 | 31 | 32 | -------------------------------------------------------------------------------- /ui/sign_in/src/main/java/com/narcissus/marketplace/ui/sign_in/SignInViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.sign_in 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.narcissus.marketplace.domain.auth.SignInResult 6 | import com.narcissus.marketplace.domain.usecase.SignInWithEmail 7 | import com.narcissus.marketplace.domain.usecase.SignInWithGoogle 8 | import kotlinx.coroutines.channels.BufferOverflow 9 | import kotlinx.coroutines.flow.MutableSharedFlow 10 | import kotlinx.coroutines.flow.asSharedFlow 11 | import kotlinx.coroutines.launch 12 | 13 | class SignInViewModel( 14 | val signInWithEmail: SignInWithEmail, 15 | val signInWithGoogle: SignInWithGoogle, 16 | ) : ViewModel() { 17 | 18 | private val _signInResultFlow = 19 | MutableSharedFlow( 20 | replay = 1, 21 | extraBufferCapacity = 1, 22 | onBufferOverflow = BufferOverflow.DROP_OLDEST, 23 | ) 24 | 25 | val signInResultFlow = _signInResultFlow.asSharedFlow() 26 | 27 | fun signInWithEmailPassword(email: String, password: String) { 28 | viewModelScope.launch { 29 | val signInResult = signInWithEmail(email, password) 30 | _signInResultFlow.emit(signInResult) 31 | } 32 | } 33 | 34 | fun signInWithGoogleAccount(idToken: String) { 35 | viewModelScope.launch { 36 | val signInResult = signInWithGoogle(idToken) 37 | _signInResultFlow.emit(signInResult) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /domain/src/main/java/com/narcissus/marketplace/domain/auth/PasswordRequirement.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.domain.auth 2 | 3 | sealed interface PasswordRequirement { 4 | fun validate(password: String): Boolean 5 | 6 | object NotBlank : PasswordRequirement { 7 | override fun validate(password: String): Boolean = 8 | password.isNotBlank() 9 | } 10 | 11 | object NotTooShort : PasswordRequirement { 12 | private const val MIN_LENGTH = 8 13 | 14 | override fun validate(password: String): Boolean = 15 | password.length > MIN_LENGTH 16 | } 17 | 18 | object HasNumber : PasswordRequirement { 19 | override fun validate(password: String): Boolean = 20 | password.any(Char::isDigit) 21 | } 22 | 23 | object HasUppercaseLetter : PasswordRequirement { 24 | override fun validate(password: String): Boolean = 25 | password.any(Char::isUpperCase) 26 | } 27 | 28 | object HasLowercaseLetter : PasswordRequirement { 29 | override fun validate(password: String): Boolean = 30 | password.any(Char::isLowerCase) 31 | } 32 | 33 | companion object { 34 | fun findFailedRequirements(password: String): List { 35 | val requirements = listOf(NotBlank, NotTooShort, HasNumber, HasUppercaseLetter, HasLowercaseLetter) 36 | return requirements 37 | .filterNot { req -> 38 | req.validate(password) 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ui/catalog/src/main/res/layout/fragment_catalog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ui/search/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | minSdk 24 11 | targetSdk 32 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles "consumer-rules.pro" 15 | } 16 | 17 | buildFeatures { 18 | viewBinding true 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation project(Modules.data) 37 | implementation project(Modules.domain) 38 | implementation project(Modules.core) 39 | 40 | implementation(Koin.koinCore) 41 | implementation(Koin.koinAndroid) 42 | testImplementation(Koin.koinTest) 43 | 44 | implementation(Navigation.navigationFragment) 45 | implementation(Navigation.navigationUi) 46 | 47 | implementation(Androidx.core) 48 | implementation(Androidx.appCompat) 49 | implementation(Google.material) 50 | testImplementation(Junit.junit) 51 | androidTestImplementation(Androidx.junit) 52 | androidTestImplementation(Androidx.espressoCore) 53 | } 54 | -------------------------------------------------------------------------------- /ui/user/src/main/java/com/narcissus/marketplace/ui/user/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.narcissus.marketplace.ui.user.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.text.TextStyle 6 | import androidx.compose.ui.unit.sp 7 | 8 | val Typography = Typography( 9 | h4 = TextStyle( 10 | fontFamily = MontserratSemiBold, 11 | fontSize = 34.sp, 12 | ), 13 | h5 = TextStyle( 14 | fontFamily = MontserratMedium, 15 | fontSize = 24.sp, 16 | ), 17 | h6 = TextStyle( 18 | fontFamily = MontserratSemiBold, 19 | fontSize = 21.sp, 20 | ), 21 | subtitle1 = TextStyle( 22 | fontFamily = MontserratMedium, 23 | fontSize = 17.sp, 24 | ), 25 | subtitle2 = TextStyle( 26 | fontFamily = MontserratMedium, 27 | fontSize = 15.sp, 28 | ), 29 | body1 = TextStyle( 30 | fontFamily = MontserratSemiBold, 31 | fontSize = 16.sp, 32 | ), 33 | body2 = TextStyle( 34 | fontFamily = Montserrat, 35 | fontSize = 14.sp, 36 | ), 37 | button = TextStyle( 38 | fontFamily = MontserratBold, 39 | fontSize = 14.sp, 40 | ), 41 | caption = TextStyle( 42 | fontFamily = Montserrat, 43 | fontSize = 12.sp, 44 | ), 45 | overline = TextStyle( 46 | fontFamily = Montserrat, 47 | fontSize = 10.sp, 48 | ), 49 | ) 50 | 51 | val TextStyle.regular: TextStyle 52 | @Composable get() = copy(fontFamily = Montserrat) 53 | -------------------------------------------------------------------------------- /.github/workflows/android_deploy_to_firebase.yml: -------------------------------------------------------------------------------- 1 | name: Build And Deploy App To Firebase Distribution 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: set up JDK 11 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 11 16 | 17 | - name: Make gradlew executable 18 | run: chmod +x ./gradlew 19 | 20 | - name: Create `google-services.json` file 21 | run: cat /home/runner/work/marketplace-app/marketplace-app/app/google-services.json | base64 22 | 23 | - name: Fill `google-services.json` with content 24 | env: 25 | DATA: ${{ secrets.GOOGLE_SERVICES_JSON }} 26 | run: echo $DATA > /home/runner/work/marketplace-app/marketplace-app/app/google-services.json 27 | 28 | - name: Add api key to local.properties 29 | env: 30 | PROPS_FILE: /home/runner/work/marketplace-app/marketplace-app/local.properties 31 | API_KEY: ${{ secrets.API_KEY }} 32 | run: echo DUMMYPRODUCTSAPIKEY=$API_KEY > $PROPS_FILE 33 | 34 | - name: build debug 35 | run: ./gradlew assembleDebug 36 | 37 | - name: Upload artifact to Firebase App Distribution 38 | uses: wzieba/Firebase-Distribution-Github-Action@v1 39 | with: 40 | appId: ${{ secrets.FIREBASE_APP_ID }} 41 | token: ${{ secrets.FIREBASE_TOKEN }} 42 | groups: test-team 43 | file: app/build/intermediates/apk/debug/app-debug.apk 44 | --------------------------------------------------------------------------------