├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── propertyfindar │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── propertyfindar │ │ │ ├── MainActivity.kt │ │ │ ├── MainFragment.kt │ │ │ ├── MainFragmentBottomNav.kt │ │ │ ├── PropertyFindARApplication.kt │ │ │ └── ui │ │ │ └── BottomNavigationFragmentStateAdapter.kt │ └── res │ │ ├── anim │ │ ├── fade_in.xml │ │ ├── fade_out.xml │ │ ├── slide_in_left.xml │ │ ├── slide_in_right.xml │ │ ├── slide_out_left.xml │ │ └── slide_out_right.xml │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── asl_bnv_account.xml │ │ ├── asl_bnv_dashboard.xml │ │ ├── asl_bnv_home.xml │ │ ├── asl_bnv_notification.xml │ │ ├── asl_bnv_settings.xml │ │ ├── avd_account.xml │ │ ├── avd_dashboard.xml │ │ ├── avd_home.xml │ │ ├── avd_notification.xml │ │ ├── bottom_navigation_tab_selector.xml │ │ ├── ic_baseline_account_circle_24.xml │ │ ├── ic_baseline_favorite_24.xml │ │ ├── ic_baseline_home_24.xml │ │ ├── ic_baseline_notifications_24.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_setting_active.xml │ │ ├── ic_settings_active_avd.xml │ │ ├── ic_settings_inactive.xml │ │ ├── vd_account_active.xml │ │ ├── vd_account_outlined.xml │ │ ├── vd_dashboard_active.xml │ │ ├── vd_dashboard_outlined.xml │ │ ├── vd_home_active.xml │ │ ├── vd_home_outlined.xml │ │ ├── vd_notification_active.xml │ │ └── vd_notification_outlined.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_main.xml │ │ ├── fragment_main_bottom_nav.xml │ │ ├── fragment_navhost_account.xml │ │ ├── fragment_navhost_dashboard.xml │ │ ├── fragment_navhost_home.xml │ │ └── fragment_navhost_notification.xml │ │ ├── menu │ │ └── menu_bottom.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ ├── nav_graph_dfm_account_start.xml │ │ ├── nav_graph_dfm_dashboard_start.xml │ │ ├── nav_graph_dfm_home_start.xml │ │ ├── nav_graph_dfm_notification_start.xml │ │ ├── nav_graph_main.xml │ │ └── nav_graph_main_bottom_nav.xml │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── smarttoolfactory │ └── propertyfindar │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ ├── Dependencies.kt │ ├── Modules.kt │ ├── Plugins.kt │ ├── Version.kt │ └── extension │ └── DependencyHandlerExtension.kt ├── config └── detekt │ └── detekt.yml ├── docs ├── android-final-architecture.png └── modules.png ├── features ├── account │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── account │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── smarttoolfactory │ │ │ │ └── account │ │ │ │ └── AccountFragment.kt │ │ └── res │ │ │ ├── layout │ │ │ └── fragment_account.xml │ │ │ └── navigation │ │ │ └── nav_graph_account.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── account │ │ └── ExampleUnitTest.kt ├── dashboard │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── smarttoolfactory │ │ │ │ └── dashboard │ │ │ │ ├── Contants.kt │ │ │ │ ├── DashboardFragment.kt │ │ │ │ ├── DashboardSeeAllFragment.kt │ │ │ │ ├── DashboardViewModel.kt │ │ │ │ ├── DashboardViewModelFactory.kt │ │ │ │ ├── adapter │ │ │ │ ├── layoutmanager │ │ │ │ │ ├── ScaledHorizontalGridLayoutManager.kt │ │ │ │ │ └── ScaledLinearLayoutManager.kt │ │ │ │ ├── model │ │ │ │ │ ├── ChartSectionModel.kt │ │ │ │ │ ├── Model.kt │ │ │ │ │ ├── PropertyListModel.kt │ │ │ │ │ └── RecommendedSectionModel.kt │ │ │ │ └── viewholder │ │ │ │ │ ├── BarChartViewBinder.kt │ │ │ │ │ ├── ChartSectionViewBinder.kt │ │ │ │ │ ├── CombinedChartViewBinder.kt │ │ │ │ │ ├── FeaturedSectionViewBinder.kt │ │ │ │ │ ├── FeaturedVideoViewBinder.kt │ │ │ │ │ ├── HorizontalPropertyViewBinder.kt │ │ │ │ │ ├── HorizontalSectionViewBinder.kt │ │ │ │ │ ├── LoadingViewBinder.kt │ │ │ │ │ ├── PropertySeeAllListViewBinder.kt │ │ │ │ │ ├── RecommendedPropertyViewBinder.kt │ │ │ │ │ └── RecommendedSectionViewBinder.kt │ │ │ │ ├── di │ │ │ │ ├── DashboardComponent.kt │ │ │ │ ├── DashboardModule.kt │ │ │ │ └── ViewModelFactory.kt │ │ │ │ └── viewbindings │ │ │ │ └── DashboardViewBindings.kt │ │ └── res │ │ │ ├── drawable │ │ │ ├── ic_baseline_architecture_24.xml │ │ │ ├── ic_baseline_bathtub_24.xml │ │ │ ├── ic_baseline_brightness_low_24.xml │ │ │ ├── ic_baseline_favorite_30.xml │ │ │ ├── ic_baseline_favorite_border_24.xml │ │ │ ├── ic_baseline_favorite_border_30.xml │ │ │ ├── ic_baseline_hotel_24.xml │ │ │ ├── ic_baseline_remove_red_eye_24.xml │ │ │ ├── ic_baseline_sort_24.xml │ │ │ └── placeholder.png │ │ │ ├── layout │ │ │ ├── fragment_bar_chart.xml │ │ │ ├── fragment_dashboard.xml │ │ │ ├── fragment_dashboard_see_all.xml │ │ │ ├── item_chart_bar.xml │ │ │ ├── item_chart_section.xml │ │ │ ├── item_chart_trends.xml │ │ │ ├── item_loading.xml │ │ │ ├── item_property_horizontal.xml │ │ │ ├── item_property_section_horizontal.xml │ │ │ ├── item_property_see_all.xml │ │ │ ├── item_recommended_property.xml │ │ │ └── item_recommended_section.xml │ │ │ └── navigation │ │ │ └── nav_graph_dashboard.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── dashboard │ │ └── ExampleUnitTest.kt ├── home │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── home │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── smarttoolfactory │ │ │ │ └── home │ │ │ │ ├── HomeFragment.kt │ │ │ │ ├── adapter │ │ │ │ ├── HomeFragmentStateAdapter.kt │ │ │ │ ├── LoadingAdapter.kt │ │ │ │ ├── model │ │ │ │ │ └── PropertyListModel.kt │ │ │ │ └── viewholder │ │ │ │ │ └── PropertyListViewBinder.kt │ │ │ │ ├── di │ │ │ │ ├── HomeComponent.kt │ │ │ │ └── HomeModule.kt │ │ │ │ ├── propertylist │ │ │ │ ├── AbstractPropertyListVM.kt │ │ │ │ ├── flow │ │ │ │ │ ├── PropertyListFragment.kt │ │ │ │ │ └── PropertyListViewModel.kt │ │ │ │ ├── paged │ │ │ │ │ ├── PagedPropertyListFragment.kt │ │ │ │ │ └── PagedPropertyListViewModel.kt │ │ │ │ └── rxjava │ │ │ │ │ ├── PropertyListFragmentRxJava3.kt │ │ │ │ │ └── PropertyListViewModelRxJava3.kt │ │ │ │ ├── viewbindings │ │ │ │ └── HomeViewBindings.kt │ │ │ │ └── viewmodel │ │ │ │ ├── HomeToolbarVM.kt │ │ │ │ └── ViewModelFactory.kt │ │ └── res │ │ │ ├── anim │ │ │ ├── fade_in.xml │ │ │ ├── fade_out.xml │ │ │ ├── slide_in_left.xml │ │ │ ├── slide_in_right.xml │ │ │ ├── slide_out_left.xml │ │ │ └── slide_out_right.xml │ │ │ ├── drawable │ │ │ ├── asl_heart_break.xml │ │ │ ├── avd_heart_empty_to_filled.xml │ │ │ ├── avd_heart_filled_to_broken.xml │ │ │ ├── ic_baseline_architecture_24.xml │ │ │ ├── ic_baseline_favorite_24.xml │ │ │ ├── ic_baseline_favorite_30.xml │ │ │ ├── ic_baseline_favorite_border_24.xml │ │ │ ├── ic_baseline_favorite_border_30.xml │ │ │ ├── ic_baseline_hotel_24.xml │ │ │ ├── ic_baseline_remove_red_eye_24.xml │ │ │ ├── ic_baseline_sort_24.xml │ │ │ ├── ic_outline_airline_seat_individual_suite_24.xml │ │ │ ├── placeholder.png │ │ │ ├── vd_heart_empty.xml │ │ │ └── vd_heart_filled.xml │ │ │ ├── layout │ │ │ ├── fragment_home.xml │ │ │ ├── fragment_navhost_property_list_flow.xml │ │ │ ├── fragment_navhost_property_list_paged.xml │ │ │ ├── fragment_navhost_property_list_rxjava3.xml │ │ │ ├── fragment_property_list.xml │ │ │ ├── fragment_property_list_paged.xml │ │ │ └── item_property_list.xml │ │ │ ├── menu │ │ │ └── menu_home.xml │ │ │ └── navigation │ │ │ ├── nav_graph_home.xml │ │ │ ├── nav_graph_property_list_flow.xml │ │ │ ├── nav_graph_property_list_paged.xml │ │ │ └── nav_graph_property_list_rxjava3.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── home │ │ ├── ExampleUnitTest.kt │ │ └── viewmodel │ │ ├── PropertyListViewModelFlowTest.kt │ │ └── PropertyListViewModelRxJava3Test.kt ├── notification │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── notification │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── smarttoolfactory │ │ │ │ └── notification │ │ │ │ └── NotificationFragment.kt │ │ └── res │ │ │ ├── layout │ │ │ └── fragment_notification.xml │ │ │ └── navigation │ │ │ └── nav_graph_notification.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── notification │ │ └── ExampleUnitTest.kt └── property_detail │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── property_detail │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── property_detail │ │ │ ├── DetailViewBindings.kt │ │ │ ├── PropertyDetailFragment.kt │ │ │ ├── PropertyDetailViewModel.kt │ │ │ └── di │ │ │ ├── PropertyDetailComponent.kt │ │ │ └── PropertyDetailModule.kt │ └── res │ │ ├── drawable │ │ └── placeholder.png │ │ ├── layout │ │ ├── fragment_property_detail.xml │ │ └── fragment_property_detail_plain.xml │ │ └── navigation │ │ └── nav_graph_property_detail.xml │ └── test │ └── java │ └── com │ └── smarttoolfactory │ └── property_detail │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libraries ├── core │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── core │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ ├── construction_process.json │ │ │ ├── home.json │ │ │ └── under_construction.json │ │ └── java │ │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── core │ │ │ ├── di │ │ │ ├── CoreModule.kt │ │ │ ├── CoreModuleDependencies.kt │ │ │ ├── DataModule.kt │ │ │ ├── qualifier │ │ │ │ └── RecycledViewPool.kt │ │ │ └── scope │ │ │ │ └── FeatureScope.kt │ │ │ ├── error │ │ │ └── NavigationException.kt │ │ │ ├── ui │ │ │ ├── fragment │ │ │ │ ├── BaseDataBindingFragment.kt │ │ │ │ ├── DynamicNavigationFragment.kt │ │ │ │ └── navhost │ │ │ │ │ ├── BaseDynamicNavHostFragment.kt │ │ │ │ │ ├── BaseNavHostFragment.kt │ │ │ │ │ └── NavHostContainerFragment.kt │ │ │ ├── recyclerview │ │ │ │ ├── adapter │ │ │ │ │ ├── ItemDiffCallback.kt │ │ │ │ │ ├── MultipleViewBinderListAdapter.kt │ │ │ │ │ ├── SingleLayoutListAdapter.kt │ │ │ │ │ ├── SingleViewBinderListAdapter.kt │ │ │ │ │ └── ViewBinders.kt │ │ │ │ └── itemcallback │ │ │ │ │ ├── DefaultItemCallback.kt │ │ │ │ │ └── PropertyItemCallback.kt │ │ │ ├── viewpager2 │ │ │ │ └── NavigableFragmentStateAdapter.kt │ │ │ └── widget │ │ │ │ └── NestedScrollableHost.kt │ │ │ ├── util │ │ │ ├── DynamicNavigationExtension.kt │ │ │ ├── EndlessScrollListener.kt │ │ │ ├── Event.kt │ │ │ ├── FlowViewStateExtension.kt │ │ │ ├── LifecycleOwnerExtension.kt │ │ │ ├── NavHostExtension.kt │ │ │ ├── RxJavaViewStateExtension.kt │ │ │ ├── ViewBindings.kt │ │ │ └── ViewExtension.kt │ │ │ ├── viewmodel │ │ │ ├── NavControllerViewModel.kt │ │ │ └── PropertyDetailNavigationVM.kt │ │ │ └── viewstate │ │ │ └── ViewState.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── core │ │ └── ExampleUnitTest.kt ├── data │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ ├── schemas │ │ └── com.smarttoolfactory.data.db.PropertyDatabase │ │ │ ├── 1.json │ │ │ ├── 2.json │ │ │ ├── 3.json │ │ │ └── 4.json │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── data │ │ │ ├── AbstractDaoTest.kt │ │ │ ├── FavoriteDaoTest.kt │ │ │ ├── PropertyDaoCoroutinesTest.kt │ │ │ ├── PropertyDaoRxJavaTest.kt │ │ │ ├── PropertyDaoTestSuite.kt │ │ │ └── UserDaoTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── data │ │ │ ├── api │ │ │ └── PropertyApi.kt │ │ │ ├── constant │ │ │ └── Constants.kt │ │ │ ├── db │ │ │ ├── PropertyDatabase.kt │ │ │ ├── converters │ │ │ │ └── PropertyTypeConverters.kt │ │ │ └── dao │ │ │ │ ├── BaseDao.kt │ │ │ │ ├── FavoritesCoroutinesDao.kt │ │ │ │ ├── FavoritesRxJava3Dao.kt │ │ │ │ ├── PagedPropertyDao.kt │ │ │ │ ├── PropertyCoroutinesDao.kt │ │ │ │ ├── PropertyRxJava3Dao.kt │ │ │ │ ├── SortOrderDAOs.kt │ │ │ │ └── UserDao.kt │ │ │ ├── di │ │ │ ├── DatabaseModule.kt │ │ │ └── NetworkModule.kt │ │ │ ├── mapper │ │ │ └── MappingFactory.kt │ │ │ ├── model │ │ │ ├── Mappables.kt │ │ │ ├── local │ │ │ │ ├── BasePropertyEntity.kt │ │ │ │ ├── BrokerEntity.kt │ │ │ │ ├── InteractivePropertyEntity.kt │ │ │ │ ├── PagedPropertyEntity.kt │ │ │ │ ├── PropertyEntity.kt │ │ │ │ ├── PropertyStatus.kt │ │ │ │ ├── SortOrderEntity.kt │ │ │ │ ├── UserEntity.kt │ │ │ │ └── UserFavoriteJoin.kt │ │ │ └── remote │ │ │ │ ├── BrokerDTO.kt │ │ │ │ ├── PropertyDTO.kt │ │ │ │ └── PropertyResponse.kt │ │ │ ├── repository │ │ │ ├── FavoritesRepoImpl.kt │ │ │ ├── FavoritesRepository.kt │ │ │ ├── PropertyRepoImpl.kt │ │ │ └── PropertyRepository.kt │ │ │ └── source │ │ │ ├── FavoritePropertyDataSource.kt │ │ │ ├── FavoritePropertyDataSourceRxJava3.kt │ │ │ ├── PropertyDataSource.kt │ │ │ └── PropertyDataSourceImpl.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── data │ │ ├── api │ │ ├── AbstractPropertyApiTest.kt │ │ ├── PropertyApiCoroutinesTest.kt │ │ └── PropertyApiRxJava3Test.kt │ │ ├── mapper │ │ └── PropertyEntityEntityListMapperTest.kt │ │ ├── repository │ │ ├── PagedPropertyRepositoryImplTest.kt │ │ ├── PropertyRepositoryCoroutinesTest.kt │ │ └── PropertyRepositoryRxJava3Test.kt │ │ └── source │ │ ├── PropertyDataSourceCoroutinesTest.kt │ │ └── PropertyDataSourceRxJava3Test.kt ├── domain │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── domain │ │ │ ├── Constants.kt │ │ │ ├── base │ │ │ └── Disposable.kt │ │ │ ├── dispatcher │ │ │ └── UseCaseDispatchers.kt │ │ │ ├── error │ │ │ └── EmptyDataException.kt │ │ │ ├── mapper │ │ │ └── Mappers.kt │ │ │ ├── model │ │ │ ├── BrokerItem.kt │ │ │ ├── Item.kt │ │ │ ├── PropertyChartItem.kt │ │ │ └── PropertyItem.kt │ │ │ └── usecase │ │ │ └── property │ │ │ ├── GetDashboardStatsUseCase.kt │ │ │ ├── GetPropertiesUseCaseFlow.kt │ │ │ ├── GetPropertiesUseCasePaged.kt │ │ │ ├── GetPropertiesUseCaseRxJava3.kt │ │ │ ├── SetPropertyStatsUseCase.kt │ │ │ └── SetPropertyStatsUseCaseRxJava3.kt │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── smarttoolfactory │ │ │ └── domain │ │ │ ├── ExampleUnitTest.kt │ │ │ ├── mapper │ │ │ └── PropertyEntityToItemListMapperTest.kt │ │ │ └── usecase │ │ │ ├── GetPropertiesUseCaseFlowTest.kt │ │ │ ├── GetPropertiesUseCaseRxJava3Test.kt │ │ │ └── TestData.kt │ │ └── resources │ │ ├── response.json │ │ ├── response_page1.json │ │ ├── response_page2.json │ │ ├── response_page3.json │ │ ├── response_sort_by_ba.json │ │ ├── response_sort_by_bd.json │ │ ├── response_sort_by_none.json │ │ ├── response_sort_by_pa.json │ │ └── response_sort_by_pd.json └── test-utils │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── test_utils │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── smarttoolfactory │ │ └── test_utils │ │ ├── TestConstans.kt │ │ ├── extension │ │ ├── RxImmediateSchedulerExtension.kt │ │ └── TestCoroutineExtension.kt │ │ ├── rule │ │ ├── MockWebServerRule.kt │ │ ├── RxImmediateSchedulerRule.kt │ │ └── TestCoroutineRule.kt │ │ ├── test_observer │ │ ├── FlowTestObserver.kt │ │ └── LiveDataTestObserver.kt │ │ └── util │ │ ├── LiveDataTestUtil.kt │ │ └── ReadResourceUtil.kt │ └── test │ ├── java │ └── com │ │ └── smarttoolfactory │ │ └── test_utils │ │ └── ExampleUnitTest.kt │ └── resources │ ├── response.json │ ├── response_page1.json │ ├── response_page2.json │ ├── response_page3.json │ ├── response_sort_by_ba.json │ ├── response_sort_by_bd.json │ ├── response_sort_by_none.json │ ├── response_sort_by_pa.json │ └── response_sort_by_pd.json ├── screenshots ├── account.png ├── dashboard.png ├── notifications.png ├── property_flow.gif ├── property_overview.gif ├── property_pagination.gif └── property_rxjava3.gif ├── scripts └── git-hooks │ └── pre-commit └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/* 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/smarttoolfactory/propertyfindar/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.propertyfindar 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.smarttoolfactory.propertyfindar", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/smarttoolfactory/propertyfindar/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.propertyfindar 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import dagger.hilt.android.AndroidEntryPoint 6 | 7 | @AndroidEntryPoint 8 | class MainActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_main) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/smarttoolfactory/propertyfindar/PropertyFindARApplication.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.propertyfindar 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class PropertyFindARApplication : Application() 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/asl_bnv_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/asl_bnv_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/asl_bnv_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/asl_bnv_notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/asl_bnv_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 11 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/avd_home.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 15 | 20 | 21 | 22 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bottom_navigation_tab_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_account_circle_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_favorite_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_home_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_notifications_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vd_account_active.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vd_account_outlined.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vd_dashboard_active.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vd_dashboard_outlined.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vd_home_active.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vd_home_outlined.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vd_notification_active.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/vd_notification_outlined.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main_bottom_nav.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_navhost_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_navhost_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_navhost_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_navhost_notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph_dfm_account_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph_dfm_dashboard_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph_dfm_home_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph_dfm_notification_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph_main_bottom_nav.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 33 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | #F5F5F5 11 | #9E9E9E 12 | #E0E0E0 13 | #F57C00 14 | #ff757575 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Property FindAR 3 | Home 4 | Dashboard 5 | Notification 6 | Account 7 | Search 8 | Property Detail 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | -------------------------------------------------------------------------------- /app/src/test/java/com/smarttoolfactory/propertyfindar/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.propertyfindar 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 | -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | /build 3 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.kotlin.dsl.`kotlin-dsl` 2 | 3 | plugins { 4 | `kotlin-dsl` 5 | } 6 | 7 | repositories { 8 | jcenter() 9 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/Modules.kt: -------------------------------------------------------------------------------- 1 | object Modules { 2 | 3 | const val APP = ":app" 4 | 5 | object AndroidLibrary { 6 | const val CORE = ":libraries:core" 7 | const val DATA = ":libraries:data" 8 | const val DOMAIN = ":libraries:domain" 9 | const val TEST_UTILS = ":libraries:test-utils" 10 | } 11 | 12 | /** 13 | * Dynamic Feature Modules 14 | */ 15 | object DynamicFeature { 16 | const val HOME = ":features:home" 17 | const val PROPERTY_DETAIL = ":features:property_detail" 18 | const val DASHBOARD = ":features:dashboard" 19 | const val NOTIFICATION = ":features:notification" 20 | const val ACCOUNT = ":features:account" 21 | const val SEARCH = ":features:search" 22 | } 23 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/Plugins.kt: -------------------------------------------------------------------------------- 1 | object Plugins { 2 | 3 | /* 4 | Project Level 5 | */ 6 | const val GRADLE = "com.android.tools.build:gradle" 7 | const val DETEKT = "io.gitlab.arturbosch.detekt" 8 | const val KTLINT = "org.jlleitschuh.gradle.ktlint" 9 | const val GIT_HOOKS = "plugins.git-hooks" 10 | 11 | const val CLASSPATH_GRADLE = "com.android.tools.build:gradle:${PluginVersion.GRADLE_VERSION}" 12 | const val CLASSPATH_KTLINT = 13 | "org.jlleitschuh.gradle:ktlint-gradle:${PluginVersion.KTLINT_VERSION}" 14 | const val CLASSPATH_DAGGER_HILT = 15 | "com.google.dagger:hilt-android-gradle-plugin:${Version.DAGGER_VERSION}" 16 | const val CLASSPATH_NAV_SAFE_ARGS = 17 | "androidx.navigation:navigation-safe-args-gradle-plugin:${PluginVersion.NAV_SAFE_ARGS_VERSION}" 18 | 19 | const val CLASSPATH_MP_CHART = "com.github.dcendents:android-maven-gradle-plugin:2.1" 20 | 21 | /* 22 | Module Level 23 | */ 24 | const val DAGGER_HILT_PLUGIN = "dagger.hilt.android.plugin" 25 | const val ANDROID_APPLICATION_PLUGIN = "com.android.application" 26 | const val ANDROID_DYNAMIC_FEATURE_PLUGIN = "com.android.dynamic-feature" 27 | const val ANDROID_LIBRARY_PLUGIN = "com.android.library" 28 | 29 | const val KOTLIN_ANDROID_PLUGIN = "kotlin-android" 30 | const val KOTLIN_ANDROID_EXTENSIONS_PLUGIN = "kotlin-android-extensions" 31 | const val KOTLIN_KAPT_PLUGIN = "kotlin-kapt" 32 | const val NAVIGATION_SAFE_ARGS = "androidx.navigation.safeargs.kotlin" 33 | } 34 | -------------------------------------------------------------------------------- /docs/android-final-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/docs/android-final-architecture.png -------------------------------------------------------------------------------- /docs/modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/docs/modules.png -------------------------------------------------------------------------------- /features/account/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/account/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extension.addBaseDynamicFeatureModuleDependencies 2 | import extension.addInstrumentationTestDependencies 3 | import extension.addUnitTestDependencies 4 | 5 | plugins { 6 | id(Plugins.ANDROID_DYNAMIC_FEATURE_PLUGIN) 7 | id(Plugins.KOTLIN_ANDROID_PLUGIN) 8 | id(Plugins.KOTLIN_ANDROID_EXTENSIONS_PLUGIN) 9 | id(Plugins.KOTLIN_KAPT_PLUGIN) 10 | id(Plugins.DAGGER_HILT_PLUGIN) 11 | } 12 | 13 | android { 14 | 15 | compileSdkVersion(AndroidVersion.COMPILE_SDK_VERSION) 16 | 17 | defaultConfig { 18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | getByName("release") { 23 | isMinifyEnabled = false 24 | proguardFiles( 25 | getDefaultProguardFile("proguard-android-optimize.txt"), 26 | "proguard-rules.pro" 27 | ) 28 | } 29 | } 30 | 31 | packagingOptions { 32 | exclude("META-INF/AL2.0") 33 | } 34 | 35 | dataBinding.isEnabled = true 36 | // android.buildFeatures.viewBinding = true 37 | 38 | compileOptions { 39 | sourceCompatibility = JavaVersion.VERSION_1_8 40 | targetCompatibility = JavaVersion.VERSION_1_8 41 | } 42 | kotlinOptions { 43 | jvmTarget = "1.8" 44 | } 45 | } 46 | 47 | dependencies { 48 | 49 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 50 | 51 | implementation(project(Modules.APP)) 52 | implementation(project(Modules.AndroidLibrary.CORE)) 53 | implementation(project(Modules.AndroidLibrary.DOMAIN)) 54 | 55 | addBaseDynamicFeatureModuleDependencies() 56 | 57 | // Support and Widgets 58 | implementation(Deps.APPCOMPAT) 59 | implementation(Deps.MATERIAL) 60 | implementation(Deps.CONSTRAINT_LAYOUT) 61 | 62 | // Glide 63 | implementation(Deps.GLIDE) 64 | kapt(Deps.GLIDE_COMPILER) 65 | 66 | // Lottie 67 | implementation(Deps.LOTTIE) 68 | 69 | // Unit Tests 70 | addUnitTestDependencies() 71 | testImplementation(project(Modules.AndroidLibrary.TEST_UTILS)) 72 | 73 | // Instrumentation Tests 74 | addInstrumentationTestDependencies() 75 | androidTestImplementation(project(Modules.AndroidLibrary.TEST_UTILS)) 76 | } 77 | -------------------------------------------------------------------------------- /features/account/src/androidTest/java/com/smarttoolfactory/account/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.account 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.smarttoolfactory.account", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /features/account/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /features/account/src/main/java/com/smarttoolfactory/account/AccountFragment.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.account 2 | 3 | import com.smarttoolfactory.account.databinding.FragmentAccountBinding 4 | import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment 5 | 6 | class AccountFragment : DynamicNavigationFragment() { 7 | 8 | override fun getLayoutRes(): Int = R.layout.fragment_account 9 | } 10 | -------------------------------------------------------------------------------- /features/account/src/main/res/layout/fragment_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 20 | 21 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /features/account/src/main/res/navigation/nav_graph_account.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /features/account/src/test/java/com/smarttoolfactory/account/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.account 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 | -------------------------------------------------------------------------------- /features/dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/dashboard/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/Contants.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard 2 | 3 | const val KEY_FAVORITES_LAYOUT_MANAGER_STATE = 4 | "favorites_layout_manager" 5 | const val KEY_MOST_VIEWED_LAYOUT_MANAGER_STATE = 6 | "views_layout_manager" 7 | const val KEY_RECOMMENDED_LAYOUT_MANAGER_STATE = 8 | "recommended_layout_manager" 9 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/DashboardViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import com.smarttoolfactory.dashboard.di.ViewModelFactory 5 | import com.smarttoolfactory.domain.usecase.property.GetDashboardStatsUseCase 6 | import com.smarttoolfactory.domain.usecase.property.SetPropertyStatsUseCase 7 | import javax.inject.Inject 8 | import kotlinx.coroutines.CoroutineScope 9 | 10 | class DashboardViewModelFactory @Inject constructor( 11 | private val coroutineScope: CoroutineScope, 12 | private val dashboardStatsUseCase: GetDashboardStatsUseCase, 13 | private val setPropertyStatsUseCase: SetPropertyStatsUseCase 14 | ) : ViewModelFactory { 15 | 16 | override fun create(handle: SavedStateHandle): DashboardViewModel { 17 | return DashboardViewModel( 18 | handle, 19 | coroutineScope, 20 | dashboardStatsUseCase, 21 | setPropertyStatsUseCase 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/adapter/layoutmanager/ScaledLinearLayoutManager.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.adapter.layoutmanager 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.LinearLayoutManager 7 | import androidx.recyclerview.widget.RecyclerView 8 | 9 | /** 10 | * Layout manager for displaying last item visible partially after total item set 11 | */ 12 | class ScaledLinearLayoutManager constructor( 13 | context: Context?, 14 | @RecyclerView.Orientation orientation: Int = RecyclerView.HORIZONTAL, 15 | reverseLayout: Boolean = false, 16 | private val totalItems: Int = 1, 17 | private val partialVisibilityRatio: Float = 0.6f 18 | ) : LinearLayoutManager(context, orientation, reverseLayout) { 19 | 20 | private val horizontalSpace get() = width - paddingStart - paddingEnd 21 | 22 | override fun generateDefaultLayoutParams() = 23 | scaledLayoutParams(super.generateDefaultLayoutParams()) 24 | 25 | override fun generateLayoutParams(lp: ViewGroup.LayoutParams?) = 26 | scaledLayoutParams(super.generateLayoutParams(lp)) 27 | 28 | override fun generateLayoutParams(c: Context?, attrs: AttributeSet?) = 29 | scaledLayoutParams(super.generateLayoutParams(c, attrs)) 30 | 31 | private fun scaledLayoutParams(layoutParams: RecyclerView.LayoutParams) = 32 | layoutParams.apply { 33 | val marginBetweenItems = 34 | (layoutParams.marginStart + layoutParams.marginEnd) * itemCount 35 | 36 | val oneItemRatio = ((100 / (totalItems.toFloat() + partialVisibilityRatio)) / 100) 37 | width = (oneItemRatio * (horizontalSpace - 0)).toInt() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/adapter/model/ChartSectionModel.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.adapter.model 2 | 3 | import com.smarttoolfactory.domain.model.PropertyChartItem 4 | 5 | data class ChartSectionModel( 6 | val items: List, 7 | val chartTitle: String = "" 8 | ) : Model 9 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/adapter/model/Model.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.adapter.model 2 | 3 | import com.smarttoolfactory.core.ui.recyclerview.adapter.MappableItemViewBinder 4 | 5 | /** 6 | * Marker for RecyclerView layout models for binding single model to, single layout with 7 | * [MappableItemViewBinder] 8 | */ 9 | interface Model 10 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/adapter/model/PropertyListModel.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.adapter.model 2 | 3 | import android.os.Parcelable 4 | import com.smarttoolfactory.domain.model.PropertyItem 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | /** 8 | * Model for list that has a title, 9 | * and data for horizontally scrollable items in RecyclerView 10 | */ 11 | @Parcelize 12 | data class PropertyListModel( 13 | val title: String = "", 14 | val items: List, 15 | var seeAll: Boolean = true 16 | ) : Model, Parcelable 17 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/adapter/model/RecommendedSectionModel.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.adapter.model 2 | 3 | import com.smarttoolfactory.domain.model.PropertyItem 4 | 5 | data class RecommendedSectionModel( 6 | val title: String = "", 7 | val items: List 8 | ) : Model 9 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/adapter/viewholder/CombinedChartViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.adapter.viewholder 2 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/adapter/viewholder/FeaturedSectionViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.adapter.viewholder 2 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/adapter/viewholder/FeaturedVideoViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.adapter.viewholder 2 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/adapter/viewholder/LoadingViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.adapter.viewholder 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.smarttoolfactory.core.ui.recyclerview.adapter.MappableItemViewBinder 8 | import com.smarttoolfactory.dashboard.R 9 | 10 | // Shown while loading 11 | object LoadingIndicator 12 | 13 | class LoadingViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) 14 | 15 | class LoadingViewBinder : MappableItemViewBinder( 16 | LoadingIndicator::class.java 17 | ) { 18 | 19 | override fun createViewHolder(parent: ViewGroup): RecyclerView.ViewHolder { 20 | return LoadingViewHolder( 21 | LayoutInflater.from(parent.context).inflate(getItemLayoutResource(), parent, false) 22 | ) 23 | } 24 | 25 | override fun bindViewHolder(model: LoadingIndicator, viewHolder: LoadingViewHolder) = Unit 26 | 27 | override fun getItemLayoutResource() = R.layout.item_loading 28 | 29 | override fun areItemsTheSame(oldItem: LoadingIndicator, newItem: LoadingIndicator) = true 30 | 31 | override fun areContentsTheSame(oldItem: LoadingIndicator, newItem: LoadingIndicator) = true 32 | } 33 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/di/DashboardComponent.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.di 2 | 3 | import androidx.fragment.app.Fragment 4 | import com.smarttoolfactory.core.di.CoreModuleDependencies 5 | import com.smarttoolfactory.dashboard.DashboardFragment 6 | import dagger.BindsInstance 7 | import dagger.Component 8 | 9 | @Component( 10 | dependencies = [CoreModuleDependencies::class], 11 | modules = [DashboardModule::class] 12 | ) 13 | interface DashboardComponent { 14 | 15 | fun inject(fragment: DashboardFragment) 16 | 17 | @Component.Factory 18 | interface Factory { 19 | fun create( 20 | dependentModule: CoreModuleDependencies, 21 | @BindsInstance fragment: Fragment 22 | ): DashboardComponent 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/di/DashboardModule.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.di 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import com.smarttoolfactory.core.di.qualifier.RecycledViewPool 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.FragmentComponent 9 | import kotlinx.coroutines.CoroutineScope 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.SupervisorJob 12 | 13 | @InstallIn(FragmentComponent::class) 14 | @Module 15 | class DashboardModule { 16 | 17 | @Provides 18 | fun provideCoroutineScope() = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) 19 | 20 | /* 21 | Shared RecycledViewPool to lower number of inflation counts in inner RecyclerViews 22 | that use same Views 23 | */ 24 | @RecycledViewPool(value = RecycledViewPool.Type.PROPERTY_HORIZONTAL) 25 | @Provides 26 | fun provideHorizontalRecycledViewPool() = RecyclerView.RecycledViewPool() 27 | 28 | @RecycledViewPool(value = RecycledViewPool.Type.CHART_ITEM) 29 | @Provides 30 | fun provideChartItemRecycledViewPool() = RecyclerView.RecycledViewPool() 31 | } 32 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/di/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | 2 | package com.smarttoolfactory.dashboard.di 3 | 4 | import android.os.Bundle 5 | import androidx.annotation.MainThread 6 | import androidx.lifecycle.AbstractSavedStateViewModelFactory 7 | import androidx.lifecycle.SavedStateHandle 8 | import androidx.lifecycle.ViewModel 9 | import androidx.savedstate.SavedStateRegistryOwner 10 | 11 | interface ViewModelFactory { 12 | fun create(handle: SavedStateHandle): V 13 | } 14 | 15 | class GenericSavedStateViewModelFactory( 16 | private val viewModelFactory: ViewModelFactory, 17 | owner: SavedStateRegistryOwner, 18 | defaultArgs: Bundle? = null 19 | ) : AbstractSavedStateViewModelFactory(owner, defaultArgs) { 20 | @Suppress("UNCHECKED_CAST") 21 | override fun create( 22 | key: String, 23 | modelClass: Class, 24 | handle: SavedStateHandle 25 | ): T { 26 | return viewModelFactory.create(handle) as T 27 | } 28 | } 29 | 30 | /** 31 | * Convenience function to use with `by viewModels` that creates an instance of 32 | * [AbstractSavedStateViewModelFactory] that enables us to pass [SavedStateHandle] 33 | * to the [ViewModel]'s constructor. 34 | * 35 | * @param factory instance of [ViewModelFactory] that will be used to construct the [ViewModel] 36 | * @param owner instance of Fragment or Activity that owns the [ViewModel] 37 | * @param defaultArgs Bundle with default values to populate the [SavedStateHandle] 38 | * 39 | * @see ViewModelFactory 40 | */ 41 | @MainThread 42 | inline fun SavedStateRegistryOwner.withFactory( 43 | factory: ViewModelFactory, 44 | defaultArgs: Bundle? = null 45 | ) = GenericSavedStateViewModelFactory(factory, this, defaultArgs) 46 | -------------------------------------------------------------------------------- /features/dashboard/src/main/java/com/smarttoolfactory/dashboard/viewbindings/DashboardViewBindings.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard.viewbindings 2 | 3 | import android.graphics.Color 4 | import android.widget.ImageButton 5 | import android.widget.ImageView 6 | import androidx.databinding.BindingAdapter 7 | import com.bumptech.glide.Glide 8 | import com.bumptech.glide.request.RequestOptions 9 | import com.smarttoolfactory.dashboard.R 10 | 11 | /** 12 | * Binding adapter used with this class android:src used with binding of this object 13 | * loads image from url into specified view 14 | * 15 | * @param view image to be loaded into 16 | * @param path of the image to be fetched 17 | */ 18 | @BindingAdapter("imageSrc") 19 | fun setImageUrl(view: ImageView, path: String?) { 20 | 21 | try { 22 | 23 | val requestOptions = RequestOptions() 24 | requestOptions.placeholder(R.drawable.placeholder) 25 | 26 | Glide 27 | .with(view.context) 28 | .setDefaultRequestOptions(requestOptions) 29 | .load(path) 30 | .into(view) 31 | } catch (e: Exception) { 32 | e.printStackTrace() 33 | } 34 | } 35 | 36 | @BindingAdapter("favoriteImageSrc") 37 | fun ImageButton.setFavoriteImageSrc(favorite: Boolean) { 38 | 39 | if (favorite) { 40 | setColorFilter(Color.rgb(244, 81, 30)) 41 | } else { 42 | setColorFilter(Color.rgb(41, 182, 246)) 43 | } 44 | 45 | val imageResource = if (favorite) R.drawable.ic_baseline_favorite_30 46 | else R.drawable.ic_baseline_favorite_border_30 47 | 48 | setImageResource(imageResource) 49 | } 50 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/ic_baseline_architecture_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/ic_baseline_bathtub_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/ic_baseline_brightness_low_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/ic_baseline_favorite_30.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/ic_baseline_favorite_border_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/ic_baseline_favorite_border_30.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/ic_baseline_hotel_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/ic_baseline_remove_red_eye_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/ic_baseline_sort_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/drawable/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/features/dashboard/src/main/res/drawable/placeholder.png -------------------------------------------------------------------------------- /features/dashboard/src/main/res/layout/fragment_bar_chart.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/layout/fragment_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 19 | 20 | 30 | 31 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/layout/fragment_dashboard_see_all.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 15 | 16 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/layout/item_chart_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/layout/item_chart_trends.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/layout/item_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/layout/item_recommended_section.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 16 | 17 | 30 | 31 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /features/dashboard/src/main/res/navigation/nav_graph_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 18 | 19 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 35 | 38 | 39 | 40 | 41 | 42 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /features/dashboard/src/test/java/com/smarttoolfactory/dashboard/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.dashboard 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 | -------------------------------------------------------------------------------- /features/home/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/home/src/androidTest/java/com/smarttoolfactory/home/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.home 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.smarttoolfactory.home", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /features/home/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /features/home/src/main/java/com/smarttoolfactory/home/adapter/LoadingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.home.adapter 2 | 3 | class LoadingAdapter 4 | -------------------------------------------------------------------------------- /features/home/src/main/java/com/smarttoolfactory/home/adapter/model/PropertyListModel.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.home.adapter.model 2 | 3 | import android.os.Parcelable 4 | import com.smarttoolfactory.domain.model.PropertyItem 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | /** 8 | * Model for list that has a title, 9 | * and data for horizontally scrollable items in RecyclerView 10 | */ 11 | @Parcelize 12 | data class PropertyListModel(val transitionName: String, val items: List) : Parcelable 13 | -------------------------------------------------------------------------------- /features/home/src/main/java/com/smarttoolfactory/home/di/HomeComponent.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.home.di 2 | 3 | import androidx.fragment.app.Fragment 4 | import com.smarttoolfactory.core.di.CoreModuleDependencies 5 | import com.smarttoolfactory.home.propertylist.flow.PropertyListFragment 6 | import com.smarttoolfactory.home.propertylist.paged.PagedPropertyListFragment 7 | import com.smarttoolfactory.home.propertylist.rxjava.PropertyListFragmentRxJava3 8 | import dagger.BindsInstance 9 | import dagger.Component 10 | 11 | @Component( 12 | dependencies = [CoreModuleDependencies::class], 13 | modules = [HomeModule::class] 14 | ) 15 | interface HomeComponent { 16 | 17 | // Fragments of ViewPager2 in Home Fragment 18 | fun inject(fragment: PropertyListFragment) 19 | fun inject(fragment: PropertyListFragmentRxJava3) 20 | fun inject(fragment: PagedPropertyListFragment) 21 | 22 | @Component.Factory 23 | interface Factory { 24 | fun create( 25 | dependentModule: CoreModuleDependencies, 26 | @BindsInstance fragment: Fragment 27 | ): HomeComponent 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /features/home/src/main/java/com/smarttoolfactory/home/propertylist/AbstractPropertyListVM.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.home.propertylist 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.ViewModel 5 | import com.smarttoolfactory.core.util.Event 6 | import com.smarttoolfactory.core.viewstate.ViewState 7 | import com.smarttoolfactory.domain.model.PropertyItem 8 | 9 | /** 10 | * Common class for multiple [ViewModel]s for [PropertyItem]s with Flow, RxJava3, and Pagination 11 | */ 12 | abstract class AbstractPropertyListVM : ViewModel() { 13 | 14 | companion object { 15 | const val PROPERTY_LIST = "PROPERTY_LIST" 16 | const val PROPERTY_DETAIL = "PROPERTY_DETAIL" 17 | } 18 | 19 | abstract val goToDetailScreen: LiveData> 20 | 21 | abstract val propertyListViewState: LiveData>> 22 | 23 | /** 24 | * Used when fragment is just opened 25 | */ 26 | abstract fun getPropertyList() 27 | 28 | abstract fun refreshPropertyList(orderBy: String? = null) 29 | 30 | abstract fun onClick(propertyItem: PropertyItem) 31 | } 32 | -------------------------------------------------------------------------------- /features/home/src/main/java/com/smarttoolfactory/home/viewbindings/HomeViewBindings.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.home.viewbindings 2 | 3 | import android.widget.ImageButton 4 | import android.widget.ImageView 5 | import androidx.databinding.BindingAdapter 6 | import com.bumptech.glide.Glide 7 | import com.bumptech.glide.request.RequestOptions 8 | 9 | /** 10 | * Binding adapter used with this class android:src used with binding of this object 11 | * loads image from url into specified view 12 | * 13 | * @param view image to be loaded into 14 | * @param path of the image to be fetched 15 | */ 16 | @BindingAdapter("imageSrc") 17 | fun setImageUrl(view: ImageView, path: String?) { 18 | 19 | try { 20 | 21 | val requestOptions = RequestOptions() 22 | 23 | Glide 24 | .with(view.context) 25 | .setDefaultRequestOptions(requestOptions) 26 | .load(path) 27 | .into(view) 28 | } catch (e: Exception) { 29 | e.printStackTrace() 30 | } 31 | } 32 | 33 | @BindingAdapter("favoriteImageSrc") 34 | fun ImageButton.setFavoriteImageSrc(favorite: Boolean) { 35 | 36 | val stateSet = 37 | intArrayOf(android.R.attr.state_checked * if (favorite) 1 else -1) 38 | setImageState(stateSet, true) 39 | 40 | // val animatedVectorDrawable = if (favorite) { 41 | // AppCompatResources.getDrawable( 42 | // context, 43 | // R.drawable.avd_heart_favorite 44 | // ) as? AnimatedVectorDrawable 45 | // } else { 46 | // AppCompatResources.getDrawable( 47 | // context, 48 | // R.drawable.avd_heart_empty 49 | // ) as? AnimatedVectorDrawable 50 | // } 51 | // setImageDrawable(animatedVectorDrawable) 52 | } 53 | -------------------------------------------------------------------------------- /features/home/src/main/java/com/smarttoolfactory/home/viewmodel/HomeToolbarVM.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.home.viewmodel 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import com.smarttoolfactory.core.util.Event 6 | import com.smarttoolfactory.domain.ORDER_BY_BEDS_ASCENDING 7 | import com.smarttoolfactory.domain.ORDER_BY_DES_DESCENDING 8 | import com.smarttoolfactory.domain.ORDER_BY_NONE 9 | import com.smarttoolfactory.domain.ORDER_BY_PRICE_ASCENDING 10 | import com.smarttoolfactory.domain.ORDER_BY_PRICE_DESCENDING 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class HomeToolbarVM @Inject constructor() : ViewModel() { 16 | 17 | var currentSortFilter = ORDER_BY_NONE 18 | 19 | val sortPropertyList = listOf( 20 | ORDER_BY_NONE, 21 | ORDER_BY_PRICE_ASCENDING, 22 | ORDER_BY_PRICE_DESCENDING, 23 | ORDER_BY_BEDS_ASCENDING, 24 | ORDER_BY_DES_DESCENDING 25 | ) 26 | 27 | val sortFilterNames = listOf( 28 | "Featured", 29 | "Price Ascending", 30 | "Price Descending", 31 | "Beds Ascending", 32 | "Beds Descending" 33 | ) 34 | 35 | val queryBySort = MutableLiveData>() 36 | } 37 | -------------------------------------------------------------------------------- /features/home/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /features/home/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /features/home/src/main/res/anim/slide_in_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /features/home/src/main/res/anim/slide_in_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /features/home/src/main/res/anim/slide_out_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /features/home/src/main/res/anim/slide_out_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/asl_heart_break.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 17 | 18 | 22 | 23 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/ic_baseline_architecture_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/ic_baseline_favorite_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/ic_baseline_favorite_30.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/ic_baseline_favorite_border_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/ic_baseline_favorite_border_30.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/ic_baseline_hotel_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/ic_baseline_remove_red_eye_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/ic_baseline_sort_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/ic_outline_airline_seat_individual_suite_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/features/home/src/main/res/drawable/placeholder.png -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/vd_heart_empty.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /features/home/src/main/res/drawable/vd_heart_filled.xml: -------------------------------------------------------------------------------- 1 | 7 | 13 | -------------------------------------------------------------------------------- /features/home/src/main/res/layout/fragment_navhost_property_list_flow.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /features/home/src/main/res/layout/fragment_navhost_property_list_paged.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /features/home/src/main/res/layout/fragment_navhost_property_list_rxjava3.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /features/home/src/main/res/menu/menu_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /features/home/src/main/res/navigation/nav_graph_property_list_flow.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 17 | 18 | 19 | 24 | 25 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /features/home/src/main/res/navigation/nav_graph_property_list_paged.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 17 | 18 | 19 | 24 | 25 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /features/home/src/main/res/navigation/nav_graph_property_list_rxjava3.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 17 | 18 | 19 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /features/home/src/test/java/com/smarttoolfactory/home/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.home 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 | -------------------------------------------------------------------------------- /features/notification/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/notification/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extension.addBaseDynamicFeatureModuleDependencies 2 | import extension.addInstrumentationTestDependencies 3 | import extension.addUnitTestDependencies 4 | 5 | plugins { 6 | id(Plugins.ANDROID_DYNAMIC_FEATURE_PLUGIN) 7 | id(Plugins.KOTLIN_ANDROID_PLUGIN) 8 | id(Plugins.KOTLIN_ANDROID_EXTENSIONS_PLUGIN) 9 | id(Plugins.KOTLIN_KAPT_PLUGIN) 10 | id(Plugins.DAGGER_HILT_PLUGIN) 11 | id(Plugins.NAVIGATION_SAFE_ARGS) 12 | } 13 | 14 | android { 15 | 16 | compileSdkVersion(AndroidVersion.COMPILE_SDK_VERSION) 17 | 18 | defaultConfig { 19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 20 | } 21 | 22 | buildTypes { 23 | getByName("release") { 24 | isMinifyEnabled = false 25 | proguardFiles( 26 | getDefaultProguardFile("proguard-android-optimize.txt"), 27 | "proguard-rules.pro" 28 | ) 29 | } 30 | } 31 | 32 | packagingOptions { 33 | exclude("META-INF/AL2.0") 34 | } 35 | 36 | dataBinding.isEnabled = true 37 | // android.buildFeatures.viewBinding = true 38 | 39 | compileOptions { 40 | sourceCompatibility = JavaVersion.VERSION_1_8 41 | targetCompatibility = JavaVersion.VERSION_1_8 42 | } 43 | kotlinOptions { 44 | jvmTarget = "1.8" 45 | } 46 | } 47 | 48 | dependencies { 49 | 50 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 51 | 52 | implementation(project(Modules.APP)) 53 | implementation(project(Modules.AndroidLibrary.CORE)) 54 | implementation(project(Modules.AndroidLibrary.DOMAIN)) 55 | 56 | addBaseDynamicFeatureModuleDependencies() 57 | 58 | // Support and Widgets 59 | implementation(Deps.APPCOMPAT) 60 | implementation(Deps.MATERIAL) 61 | implementation(Deps.CONSTRAINT_LAYOUT) 62 | 63 | // Glide 64 | implementation(Deps.GLIDE) 65 | kapt(Deps.GLIDE_COMPILER) 66 | 67 | // Lottie 68 | implementation(Deps.LOTTIE) 69 | 70 | // Unit Tests 71 | addUnitTestDependencies() 72 | testImplementation(project(Modules.AndroidLibrary.TEST_UTILS)) 73 | 74 | // Instrumentation Tests 75 | addInstrumentationTestDependencies() 76 | androidTestImplementation(project(Modules.AndroidLibrary.TEST_UTILS)) 77 | } 78 | -------------------------------------------------------------------------------- /features/notification/src/androidTest/java/com/smarttoolfactory/notification/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.notification 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.smarttoolfactory.notification", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /features/notification/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /features/notification/src/main/java/com/smarttoolfactory/notification/NotificationFragment.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.notification 2 | 3 | import com.smarttoolfactory.core.ui.fragment.DynamicNavigationFragment 4 | import com.smarttoolfactory.notification.databinding.FragmentNotificationBinding 5 | 6 | class NotificationFragment : DynamicNavigationFragment() { 7 | 8 | override fun getLayoutRes(): Int = R.layout.fragment_notification 9 | } 10 | -------------------------------------------------------------------------------- /features/notification/src/main/res/layout/fragment_notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 22 | 23 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /features/notification/src/main/res/navigation/nav_graph_notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /features/notification/src/test/java/com/smarttoolfactory/notification/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.notification 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 | -------------------------------------------------------------------------------- /features/property_detail/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/property_detail/src/androidTest/java/com/smarttoolfactory/property_detail/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.property_detail 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.smarttoolfactory.property_detail", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /features/property_detail/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /features/property_detail/src/main/java/com/smarttoolfactory/property_detail/DetailViewBindings.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.property_detail 2 | 3 | import android.widget.ImageView 4 | import androidx.databinding.BindingAdapter 5 | import com.bumptech.glide.Glide 6 | import com.bumptech.glide.request.RequestOptions 7 | 8 | /** 9 | * Binding adapter used with this class android:src used with binding of this object 10 | * loads image from url into specified view 11 | * 12 | * @param view image to be loaded into 13 | * @param path of the image to be fetched 14 | */ 15 | @BindingAdapter("imageSrc") 16 | fun setImageUrl(view: ImageView, path: String?) { 17 | 18 | try { 19 | 20 | val requestOptions = RequestOptions() 21 | requestOptions.placeholder(R.drawable.placeholder) 22 | 23 | Glide 24 | .with(view.context) 25 | .setDefaultRequestOptions(requestOptions) 26 | .load(path) 27 | .into(view) 28 | } catch (e: Exception) { 29 | e.printStackTrace() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /features/property_detail/src/main/java/com/smarttoolfactory/property_detail/PropertyDetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.property_detail 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import com.smarttoolfactory.domain.usecase.property.SetPropertyStatsUseCase 6 | import javax.inject.Inject 7 | import kotlinx.coroutines.CoroutineScope 8 | 9 | class PropertyDetailViewModel @Inject constructor( 10 | private val coroutineScope: CoroutineScope, 11 | private val setPropertyStatusUseCase: SetPropertyStatsUseCase 12 | ) : ViewModel() 13 | 14 | class PropertyDetailViewModelFactory @Inject constructor( 15 | private val coroutineScope: CoroutineScope, 16 | private val setPropertyStatusUseCase: SetPropertyStatsUseCase 17 | ) : ViewModelProvider.Factory { 18 | 19 | @Suppress("UNCHECKED_CAST") 20 | override fun create(modelClass: Class): T { 21 | if (modelClass != PropertyDetailViewModel::class.java) { 22 | throw IllegalArgumentException("Unknown ViewModel class") 23 | } 24 | return PropertyDetailViewModel( 25 | coroutineScope, 26 | setPropertyStatusUseCase 27 | ) as T 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /features/property_detail/src/main/java/com/smarttoolfactory/property_detail/di/PropertyDetailComponent.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.property_detail.di 2 | 3 | import androidx.fragment.app.Fragment 4 | import com.smarttoolfactory.core.di.CoreModuleDependencies 5 | import com.smarttoolfactory.property_detail.PropertyDetailFragment 6 | import dagger.BindsInstance 7 | import dagger.Component 8 | 9 | @Component( 10 | dependencies = [CoreModuleDependencies::class], 11 | modules = [PropertyDetailModule::class] 12 | ) 13 | interface PropertyDetailComponent { 14 | 15 | fun inject(fragment: PropertyDetailFragment) 16 | 17 | @Component.Factory 18 | interface Factory { 19 | fun create( 20 | dependentModule: CoreModuleDependencies, 21 | @BindsInstance fragment: Fragment 22 | ): PropertyDetailComponent 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /features/property_detail/src/main/java/com/smarttoolfactory/property_detail/di/PropertyDetailModule.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.property_detail.di 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.lifecycle.ViewModelProvider 5 | import com.smarttoolfactory.property_detail.PropertyDetailViewModel 6 | import com.smarttoolfactory.property_detail.PropertyDetailViewModelFactory 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.components.FragmentComponent 11 | import kotlinx.coroutines.CoroutineScope 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.SupervisorJob 14 | 15 | @InstallIn(FragmentComponent::class) 16 | @Module 17 | class PropertyDetailModule { 18 | 19 | @Provides 20 | fun providePostDetailViewModel(fragment: Fragment, factory: PropertyDetailViewModelFactory) = 21 | ViewModelProvider(fragment, factory).get(PropertyDetailViewModel::class.java) 22 | 23 | @Provides 24 | fun provideCoroutineScope() = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) 25 | } 26 | -------------------------------------------------------------------------------- /features/property_detail/src/main/res/drawable/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/features/property_detail/src/main/res/drawable/placeholder.png -------------------------------------------------------------------------------- /features/property_detail/src/main/res/navigation/nav_graph_property_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /features/property_detail/src/test/java/com/smarttoolfactory/property_detail/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.property_detail 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 | -------------------------------------------------------------------------------- /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 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 02 11:05:47 TRT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /libraries/core/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extension.addCoreModuleDependencies 2 | import extension.addInstrumentationTestDependencies 3 | import extension.addUnitTestDependencies 4 | 5 | plugins { 6 | id(Plugins.ANDROID_LIBRARY_PLUGIN) 7 | id(Plugins.KOTLIN_ANDROID_PLUGIN) 8 | id(Plugins.KOTLIN_ANDROID_EXTENSIONS_PLUGIN) 9 | id(Plugins.KOTLIN_KAPT_PLUGIN) 10 | id(Plugins.DAGGER_HILT_PLUGIN) 11 | } 12 | 13 | android { 14 | 15 | compileSdk = AndroidVersion.COMPILE_SDK_VERSION 16 | defaultConfig { 17 | minSdk = AndroidVersion.MIN_SDK_VERSION 18 | targetSdk = AndroidVersion.TARGET_SDK_VERSION 19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 20 | } 21 | 22 | buildTypes { 23 | getByName("release") { 24 | isMinifyEnabled = false 25 | proguardFiles( 26 | getDefaultProguardFile("proguard-android-optimize.txt"), 27 | "proguard-rules.pro" 28 | ) 29 | } 30 | } 31 | 32 | android.buildFeatures.dataBinding = true 33 | 34 | compileOptions { 35 | sourceCompatibility = JavaVersion.VERSION_1_8 36 | targetCompatibility = JavaVersion.VERSION_1_8 37 | } 38 | 39 | kotlinOptions { 40 | jvmTarget = "1.8" 41 | } 42 | 43 | testOptions { 44 | unitTests.isIncludeAndroidResources = true 45 | } 46 | } 47 | 48 | dependencies { 49 | 50 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 51 | 52 | implementation(project(Modules.AndroidLibrary.DOMAIN)) 53 | implementation(project(Modules.AndroidLibrary.DATA)) 54 | 55 | addCoreModuleDependencies() 56 | 57 | addUnitTestDependencies() 58 | testImplementation(project(Modules.AndroidLibrary.TEST_UTILS)) 59 | 60 | addInstrumentationTestDependencies() 61 | androidTestImplementation(project(Modules.AndroidLibrary.TEST_UTILS)) 62 | } 63 | -------------------------------------------------------------------------------- /libraries/core/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/libraries/core/consumer-rules.pro -------------------------------------------------------------------------------- /libraries/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.kts. 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 -------------------------------------------------------------------------------- /libraries/core/src/androidTest/java/com/smarttoolfactory/core/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core 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.smarttoolfactory.core.test", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libraries/core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/di/CoreModule.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.di 2 | 3 | import com.smarttoolfactory.domain.dispatcher.UseCaseDispatchers 4 | import dagger.Module 5 | import dagger.Provides 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.components.SingletonComponent 8 | import javax.inject.Singleton 9 | import kotlinx.coroutines.CoroutineScope 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.SupervisorJob 12 | 13 | @InstallIn(SingletonComponent::class) 14 | @Module(includes = [DataModule::class]) 15 | class CoreModule { 16 | 17 | @Singleton 18 | @Provides 19 | fun provideCoroutineScope() = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob()) 20 | 21 | @Provides 22 | fun provideUseCaseDispatchers(): UseCaseDispatchers { 23 | return UseCaseDispatchers(Dispatchers.IO, Dispatchers.Default, Dispatchers.Main) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/di/CoreModuleDependencies.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.di 2 | 3 | import com.smarttoolfactory.domain.usecase.property.GetDashboardStatsUseCase 4 | import com.smarttoolfactory.domain.usecase.property.GetPropertiesUseCaseFlow 5 | import com.smarttoolfactory.domain.usecase.property.GetPropertiesUseCasePaged 6 | import com.smarttoolfactory.domain.usecase.property.GetPropertiesUseCaseRxJava3 7 | import com.smarttoolfactory.domain.usecase.property.SetPropertyStatsUseCase 8 | import com.smarttoolfactory.domain.usecase.property.SetPropertyStatsUseCaseRxJava3 9 | import dagger.hilt.EntryPoint 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | 13 | /** 14 | * This component is required for adding dependencies to Dynamic Feature Modules by 15 | * adding [CoreModule] as dependent component 16 | */ 17 | @EntryPoint 18 | @InstallIn(SingletonComponent::class) 19 | interface CoreModuleDependencies { 20 | 21 | /* 22 | Provision methods to provide dependencies to components that depend on this component 23 | */ 24 | fun getPropertiesUseCaseFlow(): GetPropertiesUseCaseFlow 25 | fun getPropertiesUseCaseRxJava3(): GetPropertiesUseCaseRxJava3 26 | fun getPropertiesUseCasePaged(): GetPropertiesUseCasePaged 27 | 28 | // Set property like or view status 29 | fun setPropertyStatsUseCase(): SetPropertyStatsUseCase 30 | fun setPropertyStatsUseCaseRxJava3(): SetPropertyStatsUseCaseRxJava3 31 | 32 | // Dashboard stats for properties and info 33 | fun getDashboardStatsUseCase(): GetDashboardStatsUseCase 34 | } 35 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/di/qualifier/RecycledViewPool.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.di.qualifier 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class RecycledViewPool(val value: Type) { 8 | 9 | enum class Type { 10 | PROPERTY_ITEM, 11 | PROPERTY_HORIZONTAL, 12 | CHART_ITEM 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/di/scope/FeatureScope.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.di.scope 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Scope to be used in dynamic feature modules 7 | */ 8 | @Scope 9 | @kotlin.annotation.Retention(AnnotationRetention.RUNTIME) 10 | annotation class FeatureScope 11 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/error/NavigationException.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.error 2 | 3 | class NavigationException(override val message: String?) : Exception(message) 4 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/ui/recyclerview/adapter/ItemDiffCallback.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.ui.recyclerview.adapter 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | class ItemDiffCallback( 6 | private val viewBinders: Map 7 | ) : DiffUtil.ItemCallback() { 8 | 9 | override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean { 10 | if (oldItem::class != newItem::class) { 11 | return false 12 | } 13 | return viewBinders[oldItem::class.java]?.areItemsTheSame(oldItem, newItem) ?: false 14 | } 15 | 16 | override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean { 17 | // We know the items are the same class because [areItemsTheSame] returned true 18 | return viewBinders[oldItem::class.java]?.areContentsTheSame(oldItem, newItem) ?: false 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/ui/recyclerview/adapter/ViewBinders.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.ui.recyclerview.adapter 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.DiffUtil 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | typealias ItemClazz = Class 8 | typealias MappableItemBinder = MappableItemViewBinder 9 | typealias ItemBinder = BaseItemViewBinder 10 | 11 | /** 12 | * [MappableItemViewBinder] has 3 way relationship which can map model [Class] 13 | * either to ViewHolder type and layout type to ViewHolder type which makes 14 | * this ViewBinder unique for a layout and model type. 15 | * 16 | * * Whenever data is submitted with different types 17 | */ 18 | abstract class MappableItemViewBinder( 19 | val modelClazz: Class 20 | ) : BaseItemViewBinder() 21 | 22 | /** 23 | * ViewBinder that maps Layout to [RecyclerView.ViewHolder] which let's this ViewHolder has 24 | * model and ViewHolder couple which can only be used adapters with single layout type 25 | */ 26 | abstract class BaseItemViewBinder : DiffUtil.ItemCallback() { 27 | 28 | abstract fun createViewHolder(parent: ViewGroup): RecyclerView.ViewHolder 29 | abstract fun bindViewHolder(model: M, viewHolder: VH) 30 | abstract fun getItemLayoutResource(): Int 31 | 32 | // Having these as non abstract because not all the viewBinders are required to implement them. 33 | open fun onViewRecycled(viewHolder: VH) = Unit 34 | open fun onViewDetachedFromWindow(viewHolder: VH) = Unit 35 | } 36 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/ui/recyclerview/itemcallback/DefaultItemCallback.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.ui.recyclerview.itemcallback 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | /** 6 | * Base [DiffUtil.ItemCallback] that calculates the difference between two lists 7 | * and outputs a list of update operations that converts the first list into the second one. 8 | * 9 | * * With data classes have same items in constructor will have same **hash code** so using 10 | * hash code for [DiffUtil.ItemCallback.areContentsTheSame] 11 | */ 12 | class DefaultItemCallback : DiffUtil.ItemCallback() { 13 | 14 | override fun areItemsTheSame( 15 | oldItem: ItemType, 16 | newItem: ItemType 17 | ): Boolean { 18 | return oldItem == newItem 19 | } 20 | 21 | override fun areContentsTheSame( 22 | oldItem: ItemType, 23 | newItem: ItemType 24 | ): Boolean { 25 | return oldItem.hashCode() == newItem.hashCode() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/ui/recyclerview/itemcallback/PropertyItemCallback.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.ui.recyclerview.itemcallback 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import com.smarttoolfactory.domain.model.PropertyItem 5 | 6 | class PropertyItemCallback : DiffUtil.ItemCallback() { 7 | 8 | override fun areItemsTheSame( 9 | oldItem: PropertyItem, 10 | newItem: PropertyItem 11 | ): Boolean { 12 | return oldItem.id == newItem.id 13 | } 14 | 15 | override fun areContentsTheSame( 16 | oldItem: PropertyItem, 17 | newItem: PropertyItem 18 | ): Boolean { 19 | return oldItem.hashCode() == newItem.hashCode() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/util/EndlessScrollListener.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.util 2 | 3 | import androidx.recyclerview.widget.LinearLayoutManager 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | class EndlessScrollListener( 7 | private val linearLayoutManager: LinearLayoutManager, 8 | private val listener: ScrollToBottomListener 9 | ) : RecyclerView.OnScrollListener() { 10 | 11 | private var previousTotal = 0 12 | private var loading = true 13 | private val visibleThreshold = 8 14 | private var firstVisibleItem = 0 15 | private var visibleItemCount = 0 16 | private var totalItemCount = 0 17 | 18 | fun onRefresh() { 19 | previousTotal = 0 20 | } 21 | 22 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 23 | super.onScrolled(recyclerView, dx, dy) 24 | 25 | visibleItemCount = recyclerView.childCount 26 | totalItemCount = linearLayoutManager.itemCount 27 | firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition() 28 | 29 | if (loading) { 30 | if (totalItemCount > previousTotal) { 31 | loading = false 32 | previousTotal = totalItemCount 33 | } 34 | } 35 | if (!loading && totalItemCount - visibleItemCount 36 | <= firstVisibleItem + visibleThreshold 37 | ) { 38 | listener.onScrollToBottom() 39 | loading = true 40 | } 41 | } 42 | 43 | interface ScrollToBottomListener { 44 | fun onScrollToBottom() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/util/Event.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.smarttoolfactory.core.util 17 | 18 | import androidx.lifecycle.Observer 19 | 20 | /** 21 | * Used as a wrapper for data that is exposed via a LiveData that represents an event. 22 | */ 23 | open class Event(private val content: T) { 24 | 25 | @Suppress("MemberVisibilityCanBePrivate") 26 | var hasBeenHandled = false 27 | private set // Allow external read but not write 28 | 29 | /** 30 | * Returns the content and prevents its use again. 31 | */ 32 | fun getContentIfNotHandled(): T? { 33 | return if (hasBeenHandled) { 34 | null 35 | } else { 36 | hasBeenHandled = true 37 | content 38 | } 39 | } 40 | 41 | /** 42 | * Returns the content, even if it's already been handled. 43 | */ 44 | fun peekContent(): T = content 45 | } 46 | 47 | /** 48 | * An [Observer] for [Event]s, simplifying the pattern of checking if the [Event]'s content has 49 | * already been handled. 50 | * 51 | * [onEventUnhandledContent] is *only* called if the [Event]'s contents has not been handled. 52 | */ 53 | class EventObserver(private val onEventUnhandledContent: (T) -> Unit) : Observer> { 54 | override fun onChanged(event: Event?) { 55 | event?.getContentIfNotHandled()?.let { 56 | onEventUnhandledContent(it) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/util/FlowViewStateExtension.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.util 2 | 3 | import com.smarttoolfactory.core.viewstate.Status 4 | import com.smarttoolfactory.core.viewstate.ViewState 5 | import com.smarttoolfactory.domain.error.EmptyDataException 6 | import kotlinx.coroutines.CoroutineDispatcher 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.catch 10 | import kotlinx.coroutines.flow.emitAll 11 | import kotlinx.coroutines.flow.flowOf 12 | import kotlinx.coroutines.flow.flowOn 13 | import kotlinx.coroutines.flow.map 14 | 15 | fun Flow.convertToFlowViewState( 16 | dispatcher: CoroutineDispatcher = Dispatchers.Default 17 | ): Flow> { 18 | return this 19 | .map { list -> ViewState(status = Status.SUCCESS, data = list) } 20 | .catch { cause: Throwable -> emitAll(flowOf(ViewState(Status.ERROR, error = cause))) } 21 | .flowOn(dispatcher) 22 | } 23 | 24 | fun Flow>.convertToFlowListViewState( 25 | dispatcher: CoroutineDispatcher = Dispatchers.Default 26 | ): Flow>> { 27 | return this 28 | .map { list -> 29 | if (list.isNullOrEmpty()) { 30 | throw EmptyDataException("Data is empty") 31 | } else { 32 | ViewState(status = Status.SUCCESS, data = list) 33 | } 34 | } 35 | .catch { cause: Throwable -> emitAll(flowOf(ViewState(Status.ERROR, error = cause))) } 36 | .flowOn(dispatcher) 37 | } 38 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/util/LifecycleOwnerExtension.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.util 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.Observer 6 | 7 | fun LifecycleOwner.observe(liveData: LiveData, predicate: (T) -> Unit) { 8 | liveData.observe(this, Observer { it?.let { predicate(it) } }) 9 | } 10 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/util/NavHostExtension.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.util 2 | 3 | import androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment 4 | import androidx.navigation.fragment.NavHostFragment 5 | import com.smarttoolfactory.core.ui.fragment.navhost.FieldProperty 6 | import com.smarttoolfactory.core.viewmodel.NavControllerViewModel 7 | 8 | var DynamicNavHostFragment.viewModel: NavControllerViewModel by FieldProperty { 9 | NavControllerViewModel() 10 | } 11 | 12 | var NavHostFragment.viewModel: NavControllerViewModel by FieldProperty { 13 | NavControllerViewModel() 14 | } 15 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/util/ViewBindings.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.util 2 | 3 | import android.view.View 4 | import androidx.core.widget.ContentLoadingProgressBar 5 | import androidx.databinding.BindingAdapter 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | 9 | /** 10 | * [BindingAdapter]s for the binding items to ListAdapter. 11 | */ 12 | @BindingAdapter("app:items") 13 | fun RecyclerView.setItems(items: List?) { 14 | 15 | items?.let { 16 | println("ViewBinding setItems()") 17 | (adapter as ListAdapter<*, *>)?.submitList(items) 18 | } 19 | } 20 | 21 | /** 22 | * Display or hide a view based on a condition 23 | * 24 | * @param condition if it's true this View's visibility is set to [View.VISIBLE] 25 | */ 26 | @BindingAdapter("visibilityBasedOn") 27 | fun View.visibilityBasedOn(condition: Boolean) { 28 | visibility = if (condition) View.VISIBLE else View.GONE 29 | } 30 | 31 | @BindingAdapter("showWhen") 32 | fun ContentLoadingProgressBar.showWhen(condition: Boolean) { 33 | if (condition) show() else hide() 34 | } 35 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/util/ViewExtension.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.util 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import android.widget.ImageView 6 | import androidx.annotation.LayoutRes 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.databinding.DataBindingUtil 9 | import androidx.databinding.ViewDataBinding 10 | import com.bumptech.glide.Glide 11 | 12 | inline fun T.executeAfter(block: T.() -> Unit) { 13 | block() 14 | executePendingBindings() 15 | } 16 | 17 | inline fun ViewGroup.inflate( 18 | @LayoutRes layout: Int, 19 | attachToRoot: Boolean = false 20 | ): T = DataBindingUtil.inflate( 21 | LayoutInflater.from(context), 22 | layout, 23 | this, 24 | attachToRoot 25 | ) 26 | 27 | fun ImageView.clearResources() { 28 | if ((context as? AppCompatActivity)?.isDestroyed == true) return 29 | Glide.with(context).clear(this) 30 | setImageDrawable(null) 31 | } 32 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/viewmodel/NavControllerViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.viewmodel 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.navigation.NavController 6 | import com.smarttoolfactory.core.util.Event 7 | 8 | /** 9 | * ViewModel shared by Activity and fragmgents that contains the [NavController] belongs to 10 | * current fragment on screen. 11 | * 12 | * * Usable to change Toolbar status based on fragment currently navigated 13 | */ 14 | class NavControllerViewModel : ViewModel() { 15 | val currentNavController = MutableLiveData>() 16 | } 17 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/viewmodel/PropertyDetailNavigationVM.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.viewmodel 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import com.smarttoolfactory.core.util.Event 6 | import com.smarttoolfactory.domain.model.PropertyItem 7 | 8 | /** 9 | * ViewModel for navigating to detail from different layers of the app. When the [PropertyItem] 10 | * 11 | * of this model is set from a list fragment specified can navigate to detail screen 12 | * 13 | * * Navigate from Main fragment in **App module** 14 | * that replaces the one that contains BottomNavigationView 15 | * 16 | * * Navigate from Home fragment in **Home module** that contains ViewPager2 17 | * * Navigate from individual property list fragments 18 | */ 19 | class PropertyDetailNavigationVM : ViewModel() { 20 | 21 | /** 22 | * LiveData to navigate Property detail from MainFragment in app module 23 | */ 24 | val goToPropertyDetailFromMain = MutableLiveData>() 25 | 26 | /** 27 | * LiveData to navigate Property detail from HomeFragment in Home dynamic feature module 28 | */ 29 | val goToPropertyDetailFromHome = MutableLiveData>() 30 | } 31 | -------------------------------------------------------------------------------- /libraries/core/src/main/java/com/smarttoolfactory/core/viewstate/ViewState.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core.viewstate 2 | 3 | class ViewState( 4 | val status: Status, 5 | val data: T? = null, 6 | val error: Throwable? = null 7 | ) { 8 | 9 | fun isSuccess() = status == Status.SUCCESS 10 | 11 | fun isLoading() = status == Status.LOADING 12 | 13 | fun getErrorMessage() = error?.message 14 | 15 | fun shouldShowErrorMessage() = error != null && status == Status.ERROR 16 | } 17 | 18 | enum class Status { 19 | LOADING, 20 | SUCCESS, 21 | ERROR 22 | } 23 | -------------------------------------------------------------------------------- /libraries/core/src/test/java/com/smarttoolfactory/core/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.core 2 | 3 | import junit.framework.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 | -------------------------------------------------------------------------------- /libraries/data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/data/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/libraries/data/consumer-rules.pro -------------------------------------------------------------------------------- /libraries/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.kts. 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 -------------------------------------------------------------------------------- /libraries/data/src/androidTest/java/com/smarttoolfactory/data/AbstractDaoTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data 2 | 3 | import androidx.annotation.CallSuper 4 | import androidx.room.Room 5 | import androidx.test.core.app.ApplicationProvider 6 | import com.smarttoolfactory.data.db.PropertyDatabase 7 | import java.util.concurrent.Executors 8 | import org.junit.After 9 | import org.junit.Before 10 | 11 | open class AbstractDaoTest( 12 | private val inMemoryDatabase: Boolean = true, 13 | private val allowMainThreadQueries: Boolean = false, 14 | ) { 15 | 16 | internal lateinit var database: PropertyDatabase 17 | 18 | @CallSuper 19 | @Before 20 | open fun setUp() { 21 | 22 | // using an in-memory database because the information stored here disappears after test 23 | val builder = if (inMemoryDatabase) { 24 | Room.inMemoryDatabaseBuilder( 25 | ApplicationProvider.getApplicationContext(), PropertyDatabase::class.java 26 | ) 27 | } else { 28 | Room.databaseBuilder( 29 | ApplicationProvider.getApplicationContext(), 30 | PropertyDatabase::class.java, 31 | "test.db" 32 | ) 33 | .fallbackToDestructiveMigration() 34 | } 35 | // 🔥🔥🔥 Without this Coroutines tests with @Transaction get stuck 36 | .setTransactionExecutor(Executors.newSingleThreadExecutor()) 37 | 38 | // allowing main thread queries, just for testing 39 | if (allowMainThreadQueries) { 40 | builder.allowMainThreadQueries() 41 | } 42 | 43 | database = builder.build() 44 | } 45 | 46 | @CallSuper 47 | @After 48 | @Throws(Exception::class) 49 | open fun tearDown() { 50 | database.close() 51 | println("🍏 AbstractDaoTest tearDown()") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /libraries/data/src/androidTest/java/com/smarttoolfactory/data/PropertyDaoTestSuite.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data 2 | 3 | import org.junit.runner.RunWith 4 | import org.junit.runners.Suite 5 | 6 | // Runs all unit tests with JUnit4. 7 | @RunWith(Suite::class) 8 | @Suite.SuiteClasses( 9 | PropertyDaoCoroutinesTest::class, 10 | PropertyDaoCoroutinesTest::class 11 | ) 12 | class PropertyDaoTestSuite 13 | -------------------------------------------------------------------------------- /libraries/data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/api/PropertyApi.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.api 2 | 3 | import com.smarttoolfactory.data.constant.ORDER_BY_NONE 4 | import com.smarttoolfactory.data.model.remote.PropertyResponse 5 | import io.reactivex.rxjava3.core.Single 6 | import retrofit2.http.GET 7 | import retrofit2.http.Query 8 | 9 | interface PropertyApiCoroutines { 10 | 11 | @GET("mobileapi/search") 12 | suspend fun getPropertyResponse( 13 | @Query("ob") orderBy: String = ORDER_BY_NONE 14 | ): PropertyResponse 15 | 16 | @GET("mobileapi/search") 17 | suspend fun getPropertyResponseForPage( 18 | @Query("page") page: Int, 19 | @Query("ob") orderBy: String = ORDER_BY_NONE 20 | ): PropertyResponse 21 | } 22 | 23 | interface PropertyApiRxJava { 24 | 25 | @GET("mobileapi/search") 26 | fun getPropertyResponse( 27 | @Query("ob") orderBy: String = ORDER_BY_NONE 28 | ): Single 29 | 30 | @GET("mobileapi/search") 31 | fun getPropertyResponseForPage( 32 | @Query("page") page: Int, 33 | @Query("ob") orderBy: String = ORDER_BY_NONE 34 | ): Single 35 | } 36 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/constant/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.constant 2 | 3 | /* 4 | Web Service Constants 5 | */ 6 | 7 | // Base Url 8 | const val BASE_URL = "https://www.propertyfinder.ae/" 9 | 10 | // Filter Types 11 | const val KEY_FILTER = "filter-key" 12 | const val ORDER_BY_NONE = "" 13 | const val ORDER_BY_PRICE_ASCENDING = "pa" 14 | const val ORDER_BY_PRICE_DESCENDING = "pd" 15 | const val ORDER_BY_BEDS_ASCENDING = "ba" 16 | const val ORDER_BY_DES_DESCENDING = "bd" 17 | 18 | /* 19 | DBConstants 20 | */ 21 | const val DATABASE_NAME = "property.db" 22 | const val DATABASE_VERSION = 4 23 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/db/converters/PropertyTypeConverters.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.db.converters 2 | 3 | import androidx.room.TypeConverter 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | import com.smarttoolfactory.data.model.local.BrokerEntity 7 | 8 | class PropertyTypeConverters { 9 | 10 | @TypeConverter 11 | fun fromBrokerEntity(data: BrokerEntity?): String? { 12 | return Gson().toJson(data) 13 | } 14 | 15 | @TypeConverter 16 | fun toBrokerEntity(json: String?): BrokerEntity? { 17 | return Gson().fromJson(json, BrokerEntity::class.java) 18 | } 19 | 20 | @TypeConverter 21 | fun fromStringList(list: List?): String? { 22 | return Gson().toJson(list) 23 | } 24 | 25 | @TypeConverter 26 | fun toStringList(json: String?): List? { 27 | val listType = object : TypeToken>() {}.type 28 | return Gson().fromJson(json, listType) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/db/dao/PagedPropertyDao.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.db.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Delete 5 | import androidx.room.Query 6 | import com.smarttoolfactory.data.model.local.PagedPropertyEntity 7 | 8 | @Dao 9 | interface PagedPropertyDao : BaseCoroutinesDao { 10 | 11 | @Delete 12 | suspend fun deletePagedPropertyEntity(entity: PagedPropertyEntity): Int 13 | 14 | @Query("DELETE FROM paged_property") 15 | suspend fun deleteAll() 16 | 17 | /** 18 | * Get number of properties in db 19 | */ 20 | @Query("SELECT COUNT(*) FROM paged_property") 21 | suspend fun getPropertyCount(): Int 22 | 23 | /** 24 | * Get properties from database. 25 | * 26 | * *If database is empty returns empty list [] 27 | */ 28 | @Query("SELECT * FROM paged_property") 29 | suspend fun getPropertyList(): List 30 | } 31 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/db/dao/PropertyCoroutinesDao.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.db.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Delete 5 | import androidx.room.Query 6 | import com.smarttoolfactory.data.model.local.PropertyEntity 7 | 8 | @Dao 9 | interface PropertyCoroutinesDao : BaseCoroutinesDao { 10 | 11 | @Delete 12 | suspend fun deletePropertyEntity(entity: PropertyEntity): Int 13 | 14 | @Query("DELETE FROM property") 15 | suspend fun deleteAll() 16 | 17 | /** 18 | * Get number of properties in db 19 | */ 20 | @Query("SELECT COUNT(*) FROM property") 21 | suspend fun getPropertyCount(): Int 22 | 23 | /** 24 | * Get properties from database. 25 | * 26 | * *If database is empty returns empty list [] 27 | */ 28 | @Query("SELECT * FROM property") 29 | suspend fun getPropertyList(): List 30 | } 31 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/db/dao/PropertyRxJava3Dao.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.db.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Delete 5 | import androidx.room.Insert 6 | import androidx.room.OnConflictStrategy 7 | import androidx.room.Query 8 | import com.smarttoolfactory.data.model.local.PropertyEntity 9 | import io.reactivex.rxjava3.core.Completable 10 | import io.reactivex.rxjava3.core.Maybe 11 | import io.reactivex.rxjava3.core.Observable 12 | import io.reactivex.rxjava3.core.Single 13 | 14 | @Dao 15 | interface PropertyRxJava3Dao : BaseRxDao { 16 | 17 | @Insert(onConflict = OnConflictStrategy.REPLACE) 18 | fun insert(propertyEntity: PropertyEntity): Completable 19 | 20 | @Insert(onConflict = OnConflictStrategy.REPLACE) 21 | fun insert(propertyEntityList: List): Completable 22 | 23 | @Delete 24 | fun deleteProperty(entity: PropertyEntity): Single 25 | 26 | @Query("DELETE FROM property") 27 | fun deleteAll(): Completable 28 | 29 | /** 30 | * Get number of properties in db 31 | */ 32 | @Query("SELECT COUNT(*) FROM property") 33 | fun getPropertyCount(): Maybe 34 | 35 | /** 36 | * Get list of [PropertyEntity]s to from database. 37 | */ 38 | @Query("SELECT * FROM property") 39 | fun getPropertiesSingle(): Single> 40 | 41 | /** 42 | * Get list of [PropertyEntity]s to from database. 43 | */ 44 | @Query("SELECT * FROM property") 45 | fun getPropertiesMaybe(): Maybe> 46 | 47 | /** 48 | * Get list of [PropertyEntity]s to from database. 49 | */ 50 | @Query("SELECT * FROM property") 51 | fun getProperties(): Observable> 52 | } 53 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/db/dao/SortOrderDAOs.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.db.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import com.smarttoolfactory.data.model.local.SortOrderEntity 8 | import io.reactivex.rxjava3.core.Completable 9 | import io.reactivex.rxjava3.core.Single 10 | 11 | @Dao 12 | interface SortOrderDaoCoroutines { 13 | 14 | @Insert(onConflict = OnConflictStrategy.REPLACE) 15 | suspend fun insert(entity: SortOrderEntity): Long 16 | 17 | @Query("SELECT order_by FROM sort_order") 18 | suspend fun getSortOrderEntity(): String 19 | } 20 | 21 | @Dao 22 | interface SortOrderDaoRxJava3 { 23 | 24 | @Insert(onConflict = OnConflictStrategy.REPLACE) 25 | fun insert(propertyEntity: SortOrderEntity): Completable 26 | 27 | @Query("SELECT order_by FROM sort_order") 28 | fun getSortOrderSingle(): Single 29 | } 30 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/db/dao/UserDao.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.db.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | import com.smarttoolfactory.data.model.local.UserEntity 6 | 7 | /** 8 | * This interface is used for CRUD operation on database 9 | */ 10 | @Dao 11 | interface UserDao : BaseCoroutinesDao { 12 | 13 | @Query("SELECT * FROM user") 14 | suspend fun getAllUsers(): List 15 | 16 | @Query("SELECT * FROM user WHERE user.userId =:id") 17 | suspend fun getUserById(id: Long): UserEntity? 18 | 19 | @Query("DELETE FROM user") 20 | suspend fun deleteAll() 21 | } 22 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/model/Mappables.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.model 2 | 3 | /** 4 | * Interface to create common behavior between entities to be able to use generics to create [Mapper] 5 | */ 6 | interface IEntity : Mappable 7 | 8 | /** 9 | * Interface to create common behavior between DTOs to be able to use generics to create [Mapper] 10 | */ 11 | interface DataTransferObject : Mappable 12 | 13 | /** 14 | * Marker interface to mark classes that implement, or interfaces that extend this interface 15 | * as convertible from one [Mappable] type to another 16 | */ 17 | interface Mappable 18 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/model/local/BrokerEntity.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.model.local 2 | 3 | import com.smarttoolfactory.data.model.IEntity 4 | 5 | data class BrokerEntity( 6 | val id: Int, 7 | val name: String, 8 | val address: String, 9 | val phone: String, 10 | val phoneExtension: String, 11 | val email: String, 12 | 13 | val mobile: String?, 14 | val agentPhoto: String?, 15 | val agentName: String, 16 | 17 | val leadEmailReceivers: List, 18 | val license: String?, 19 | val agentId: Int, 20 | val logo: String? 21 | ) : IEntity 22 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/model/local/PropertyStatus.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.model.local 2 | 3 | // @Entity( 4 | // tableName = "favorites", 5 | // indices = [Index(value = ["userAccountId", "postId"])], 6 | // foreignKeys = [ 7 | // ForeignKey( 8 | // entity = FavoriteEntity::class, 9 | // parentColumns = ["id"], 10 | // childColumns = ["userAccountId"], 11 | // onDelete = ForeignKey.NO_ACTION 12 | // ) 13 | // ] 14 | // ) 15 | // data class FavoritesEntities( 16 | // @PrimaryKey(autoGenerate = true) 17 | // val id: Long = 0, 18 | // val userAccountId: Long = NO_ACCOUNT, 19 | // val propertyId: Int, 20 | // val displayCount: Int = 0, 21 | // val favorite: Boolean = false 22 | // ) 23 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/model/local/SortOrderEntity.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.model.local 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import com.smarttoolfactory.data.constant.ORDER_BY_NONE 7 | 8 | @Entity(tableName = "sort_order") 9 | data class SortOrderEntity( 10 | 11 | @PrimaryKey 12 | @ColumnInfo(name = "id") 13 | val id: Int = 0, 14 | 15 | @ColumnInfo(name = "order_by", defaultValue = ORDER_BY_NONE) 16 | val orderBy: String = ORDER_BY_NONE 17 | ) 18 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/model/local/UserEntity.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.model.local 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import com.smarttoolfactory.data.model.IEntity 6 | 7 | @Entity(tableName = "user") 8 | data class UserEntity( 9 | @PrimaryKey 10 | val userId: Long, 11 | val firstName: String, 12 | val lastName: String, 13 | val email: String, 14 | val password: String 15 | ) : IEntity 16 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/model/remote/BrokerDTO.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.model.remote 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.smarttoolfactory.data.model.DataTransferObject 5 | 6 | data class BrokerDTO( 7 | @SerializedName("id") 8 | val id: Int, 9 | @SerializedName("name") 10 | val name: String, 11 | @SerializedName("address") 12 | val address: String, 13 | @SerializedName("phone") 14 | val phone: String, 15 | @SerializedName("phone_extension") 16 | val phoneExtension: String, 17 | @SerializedName("email") 18 | val email: String, 19 | 20 | @SerializedName("mobile") 21 | val mobile: String?, 22 | 23 | @SerializedName("agent_photo") 24 | val agentPhoto: String?, 25 | @SerializedName("agent_name") 26 | val agentName: String, 27 | 28 | @SerializedName("lead_email_receivers") 29 | val leadEmailReceivers: List, 30 | @SerializedName("license") 31 | val license: String?, 32 | @SerializedName("agent_id") 33 | val agentId: Int, 34 | @SerializedName("logo") 35 | val logo: String? 36 | ) : DataTransferObject 37 | -------------------------------------------------------------------------------- /libraries/data/src/main/java/com/smarttoolfactory/data/model/remote/PropertyResponse.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.model.remote 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.smarttoolfactory.data.model.DataTransferObject 5 | 6 | data class PropertyResponse( 7 | @SerializedName("total") 8 | val total: Int, 9 | @SerializedName("res") 10 | val res: List 11 | ) : DataTransferObject 12 | -------------------------------------------------------------------------------- /libraries/data/src/test/java/com/smarttoolfactory/data/mapper/PropertyEntityEntityListMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.data.mapper 2 | 3 | import com.google.common.truth.Truth 4 | import com.smarttoolfactory.data.model.remote.PropertyResponse 5 | import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH 6 | import com.smarttoolfactory.test_utils.util.convertToObjectFromJson 7 | import com.smarttoolfactory.test_utils.util.getResourceAsText 8 | import org.junit.jupiter.api.Test 9 | 10 | class PropertyEntityEntityListMapperTest { 11 | 12 | private val propertyDTOResponse by lazy { 13 | convertToObjectFromJson(getResourceAsText(RESPONSE_JSON_PATH))!! 14 | } 15 | 16 | @Test 17 | fun `given a valid propertyDTO list, should map to propertyEntity list`() { 18 | 19 | val propertyDTOList = propertyDTOResponse.res 20 | 21 | // GIVEN 22 | val mapper = MapperFactory.createListMapper() 23 | 24 | // WHEN 25 | val actual = mapper.map(propertyDTOList) 26 | 27 | // THEN 28 | actual.forEachIndexed { index, propertyEntity -> 29 | Truth.assertThat(propertyEntity.id).isEqualTo(propertyDTOList[index].id) 30 | Truth.assertThat(propertyEntity.update).isEqualTo(propertyDTOList[index].update) 31 | Truth.assertThat(propertyEntity.price).isEqualTo(propertyDTOList[index].price) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libraries/domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extension.addUnitTestDependencies 2 | 3 | plugins { 4 | id(Plugins.ANDROID_LIBRARY_PLUGIN) 5 | id(Plugins.KOTLIN_ANDROID_PLUGIN) 6 | id(Plugins.KOTLIN_ANDROID_EXTENSIONS_PLUGIN) 7 | id(Plugins.KOTLIN_KAPT_PLUGIN) 8 | id(Plugins.DAGGER_HILT_PLUGIN) 9 | } 10 | 11 | android { 12 | 13 | compileSdkVersion(AndroidVersion.COMPILE_SDK_VERSION) 14 | defaultConfig { 15 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | getByName("release") { 20 | isMinifyEnabled = false 21 | proguardFiles( 22 | getDefaultProguardFile("proguard-android-optimize.txt"), 23 | "proguard-rules.pro" 24 | ) 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility = JavaVersion.VERSION_1_8 29 | targetCompatibility = JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = "1.8" 33 | } 34 | } 35 | 36 | dependencies { 37 | 38 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 39 | 40 | implementation(project(Modules.AndroidLibrary.DATA)) 41 | 42 | implementation(Deps.KOTLIN) 43 | implementation(Deps.ANDROIDX_CORE_KTX) 44 | 45 | // Dagger 46 | implementation(Deps.DAGGER_HILT_ANDROID) 47 | kapt(Deps.DAGGER_HILT_COMPILER) 48 | 49 | // RxJava 50 | implementation(Deps.RX_JAVA3) 51 | // RxAndroid 52 | implementation(Deps.RX_JAVA3_ANDROID) 53 | 54 | // Coroutines 55 | implementation(Deps.COROUTINES_CORE) 56 | implementation(Deps.COROUTINES_ANDROID) 57 | 58 | testImplementation(Deps.GSON) 59 | 60 | addUnitTestDependencies() 61 | testImplementation(project(Modules.AndroidLibrary.TEST_UTILS)) 62 | } 63 | -------------------------------------------------------------------------------- /libraries/domain/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/libraries/domain/consumer-rules.pro -------------------------------------------------------------------------------- /libraries/domain/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.kts. 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 -------------------------------------------------------------------------------- /libraries/domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/domain/src/main/java/com/smarttoolfactory/domain/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain 2 | 3 | const val ORDER_BY_NONE = com.smarttoolfactory.data.constant.ORDER_BY_NONE 4 | const val ORDER_BY_PRICE_ASCENDING = com.smarttoolfactory.data.constant.ORDER_BY_PRICE_ASCENDING 5 | const val ORDER_BY_PRICE_DESCENDING = com.smarttoolfactory.data.constant.ORDER_BY_PRICE_DESCENDING 6 | const val ORDER_BY_BEDS_ASCENDING = com.smarttoolfactory.data.constant.ORDER_BY_BEDS_ASCENDING 7 | const val ORDER_BY_DES_DESCENDING = com.smarttoolfactory.data.constant.ORDER_BY_DES_DESCENDING 8 | -------------------------------------------------------------------------------- /libraries/domain/src/main/java/com/smarttoolfactory/domain/base/Disposable.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain.base 2 | 3 | /** 4 | * Interface for adding common behavior for any class that has items that need to be disposed 5 | * such as RxJava Observables or Coroutines jobs. 6 | */ 7 | interface Disposable { 8 | 9 | fun dispose() 10 | } 11 | -------------------------------------------------------------------------------- /libraries/domain/src/main/java/com/smarttoolfactory/domain/dispatcher/UseCaseDispatchers.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain.dispatcher 2 | 3 | import io.reactivex.rxjava3.core.Observable 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | /** 9 | * Class for providing dispatcher for different thread operations. This class is useful 10 | * in tests to control every thread in one place. 11 | * 12 | * Multiple flowOn operators fuse to a single flowOn with a combined context. 13 | * The elements of the context of the first flowOn operator naturally take precedence over 14 | * 15 | * the elements of the second flowOn operator when they have the same context keys, for example: 16 | * ``` 17 | * flow.map { ... } // Will be executed in IO 18 | * .flowOn(Dispatchers.IO) // This one takes precedence 19 | * .flowOn(Dispatchers.Default) 20 | * ``` 21 | * 22 | * ### Note: While [Observable.observeOn] runs down stream, [Flow.flowOn] works upstream 23 | */ 24 | 25 | data class UseCaseDispatchers( 26 | val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, 27 | val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default, 28 | val mainDispatcher: CoroutineDispatcher = Dispatchers.Main, 29 | ) 30 | -------------------------------------------------------------------------------- /libraries/domain/src/main/java/com/smarttoolfactory/domain/error/EmptyDataException.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain.error 2 | 3 | /** 4 | * Exception for indicating there is no data retrieved from remote or local source 5 | */ 6 | class EmptyDataException(message: String) : Exception(message) 7 | -------------------------------------------------------------------------------- /libraries/domain/src/main/java/com/smarttoolfactory/domain/model/BrokerItem.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain.model 2 | 3 | import android.os.Parcelable 4 | import kotlinx.android.parcel.Parcelize 5 | 6 | @Parcelize 7 | data class BrokerItem( 8 | val id: Int, 9 | val name: String, 10 | val address: String, 11 | val phone: String, 12 | val phoneExtension: String, 13 | val email: String, 14 | 15 | val mobile: String?, 16 | val agentPhoto: String?, 17 | val agentName: String, 18 | 19 | val leadEmailReceivers: List, 20 | val license: String?, 21 | val agentId: Int, 22 | val logo: String? 23 | ) : Item, Parcelable 24 | -------------------------------------------------------------------------------- /libraries/domain/src/main/java/com/smarttoolfactory/domain/model/Item.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain.model 2 | 3 | import android.os.Parcelable 4 | import com.smarttoolfactory.data.model.Mappable 5 | 6 | interface Item : Parcelable, Mappable { 7 | 8 | /** 9 | * Data classes have same hash code if their constructor has the same objects 10 | * 11 | * * Data classes have 3 common properties if properties of constructor is the same 12 | * * Hash code 13 | * * Equals 14 | * * toString 15 | */ 16 | fun dataEquals(other: Item?): Boolean { 17 | return this.hashCode() == other.hashCode() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libraries/domain/src/main/java/com/smarttoolfactory/domain/model/PropertyChartItem.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain.model 2 | 3 | data class PropertyChartItem( 4 | val id: Int, 5 | val title: String, 6 | val price: Float, 7 | val currency: String, 8 | private val beds: String = "", 9 | private val baths: String = "", 10 | val location: String 11 | ) { 12 | 13 | val bedRooms = try { 14 | beds.toInt() 15 | } catch (e: NumberFormatException) { 16 | -1 17 | } 18 | 19 | val bathRooms = try { 20 | baths.toInt() 21 | } catch (e: NumberFormatException) { 22 | -1 23 | } 24 | 25 | var position: Int = 0 26 | } 27 | -------------------------------------------------------------------------------- /libraries/domain/src/main/java/com/smarttoolfactory/domain/model/PropertyItem.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain.model 2 | 3 | import android.os.Parcelable 4 | import kotlinx.android.parcel.Parcelize 5 | 6 | /** 7 | * UI item for Properties 8 | */ 9 | @Parcelize 10 | data class PropertyItem( 11 | 12 | val id: Int, 13 | val update: Int, 14 | val categoryId: Int, 15 | val title: String, 16 | val subject: String, 17 | val type: String, 18 | val typeId: Int, 19 | val thumbnail: String?, 20 | val thumbnailBig: String?, 21 | val imageCount: Int, 22 | val price: String, 23 | val pricePeriod: String?, 24 | val pricePeriodRaw: String, 25 | val priceLabel: String?, 26 | val priceValue: String?, 27 | val priceValueRaw: Int, 28 | val currency: String, 29 | val featured: Boolean, 30 | val location: String, 31 | val area: String, 32 | val poa: Boolean, 33 | val reraPermit: String?, 34 | val bathrooms: String, 35 | val bedrooms: String, 36 | val dateInsert: String, 37 | val dateUpdate: String, 38 | val agentName: String, 39 | val brokerName: String, 40 | val agentLicense: String?, 41 | val locationId: Int, 42 | val hideLocation: Boolean, 43 | 44 | val broker: BrokerItem, 45 | val amenities: List, 46 | val amenitiesKeys: List, 47 | 48 | val latitude: Double, 49 | val longitude: Double, 50 | val premium: Boolean, 51 | val livingrooms: String, 52 | val verified: Boolean, 53 | 54 | val gallery: List?, 55 | val phone: String, 56 | 57 | val leadEmailReceivers: List, 58 | 59 | val reference: String, 60 | 61 | var viewCount: Int = 0, 62 | var isFavorite: Boolean = false, 63 | /* 64 | This should be a unique transition id for to have shared transitions between fragments. 65 | For multiple RecyclerViews with same items it should have prefix to separate views. 66 | */ 67 | var transitionName: String = id.toString() 68 | ) : Parcelable 69 | -------------------------------------------------------------------------------- /libraries/domain/src/test/java/com/smarttoolfactory/domain/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain 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 | -------------------------------------------------------------------------------- /libraries/domain/src/test/java/com/smarttoolfactory/domain/mapper/PropertyEntityToItemListMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain.mapper 2 | 3 | import com.smarttoolfactory.data.mapper.MapperFactory 4 | import com.smarttoolfactory.data.mapper.PropertyDTOtoEntityListMapper 5 | import com.smarttoolfactory.data.model.remote.PropertyResponse 6 | import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH 7 | import com.smarttoolfactory.test_utils.util.convertToObjectFromJson 8 | import com.smarttoolfactory.test_utils.util.getResourceAsText 9 | 10 | class PropertyEntityToItemListMapperTest { 11 | 12 | companion object { 13 | 14 | private val propertyDTOList by lazy { 15 | convertToObjectFromJson(getResourceAsText(RESPONSE_JSON_PATH))!!.res 16 | } 17 | 18 | private val entityList = 19 | MapperFactory.createListMapper() 20 | .map(propertyDTOList) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libraries/domain/src/test/java/com/smarttoolfactory/domain/usecase/TestData.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.domain.usecase 2 | 3 | import com.smarttoolfactory.data.mapper.MapperFactory 4 | import com.smarttoolfactory.data.mapper.PropertyDTOtoEntityListMapper 5 | import com.smarttoolfactory.data.model.remote.PropertyResponse 6 | import com.smarttoolfactory.domain.mapper.PropertyEntityToItemListMapper 7 | import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH 8 | import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_1 9 | import com.smarttoolfactory.test_utils.RESPONSE_JSON_PATH_PAGE_2 10 | import com.smarttoolfactory.test_utils.util.convertToObjectFromJson 11 | import com.smarttoolfactory.test_utils.util.getResourceAsText 12 | 13 | object TestData { 14 | 15 | /* 16 | DTOs 17 | */ 18 | private val propertyResponse = convertToObjectFromJson( 19 | getResourceAsText(RESPONSE_JSON_PATH) 20 | )!! 21 | 22 | private val propertyDTOList = propertyResponse.res 23 | 24 | private val propertyResponsePage1 = convertToObjectFromJson( 25 | getResourceAsText(RESPONSE_JSON_PATH_PAGE_1) 26 | )!! 27 | 28 | private val propertyResponsePage2 = convertToObjectFromJson( 29 | getResourceAsText(RESPONSE_JSON_PATH_PAGE_2) 30 | )!! 31 | 32 | /* 33 | Properties 34 | */ 35 | private val entityMapper = MapperFactory.createListMapper() 36 | 37 | val entityList = entityMapper.map(propertyDTOList) 38 | val entityListPage1 = entityMapper.map(propertyResponsePage1.res) 39 | val entityListPage2 = entityMapper.map(propertyResponsePage2.res) 40 | 41 | /* 42 | Items 43 | */ 44 | val itemMapper = MapperFactory.createListMapper() 45 | 46 | val itemList = itemMapper.map(entityList) 47 | val itemListPage1 = itemMapper.map(entityListPage1) 48 | val itemListPage2 = itemMapper.map(entityListPage2) 49 | } 50 | -------------------------------------------------------------------------------- /libraries/test-utils/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/test-utils/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/libraries/test-utils/consumer-rules.pro -------------------------------------------------------------------------------- /libraries/test-utils/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.kts. 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 -------------------------------------------------------------------------------- /libraries/test-utils/src/androidTest/java/com/smarttoolfactory/test_utils/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | // package com.smarttoolfactory.test_utils 2 | // 3 | // import androidx.test.ext.junit.runners.AndroidJUnit4 4 | // import androidx.test.platform.app.InstrumentationRegistry 5 | // import org.junit.Test 6 | // import org.junit.runner.RunWith 7 | // 8 | // /** 9 | // * Instrumented test, which will execute on an Android device. 10 | // * 11 | // * See [testing documentation](http://d.android.com/tools/testing). 12 | // */ 13 | // @RunWith(AndroidJUnit4::class) 14 | // class ExampleInstrumentedTest { 15 | // @Test 16 | // fun useAppContext() { 17 | // // Context of the app under test. 18 | // val appContext = InstrumentationRegistry.getInstrumentation().targetContext 19 | // assertEquals("com.smarttoolfactory.test_shared.test", appContext.packageName) 20 | // } 21 | // } 22 | -------------------------------------------------------------------------------- /libraries/test-utils/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/TestConstans.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.test_utils 2 | 3 | const val RESPONSE_JSON_PATH = "response_sort_by_none.json" 4 | const val RESPONSE_JSON_PATH_ORDER_BY_PRICE_ASCENDING = "response_sort_by_pa.json" 5 | const val RESPONSE_JSON_PATH_ORDER_BY_PRICE_DESCENDING = "response_sort_by_pd.json" 6 | const val RESPONSE_JSON_PATH_ORDER_BY_BEDS_ASCENDING = "response_sort_by_ba.json" 7 | const val RESPONSE_JSON_PATH_ORDER_BY_BEDS_DESCENDING = "response_sort_by_bd.json" 8 | const val RESPONSE_JSON_PATH_PAGE_1 = "response_page1.json" 9 | const val RESPONSE_JSON_PATH_PAGE_2 = "response_page2.json" 10 | const val RESPONSE_JSON_PATH_PAGE_3 = "response_page3.json" 11 | const val SERVER_INTERNAL_ERROR_MESSAGE = "Unexpected error occurred" 12 | -------------------------------------------------------------------------------- /libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/extension/RxImmediateSchedulerExtension.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.test_utils.extension 2 | 3 | import io.reactivex.rxjava3.android.plugins.RxAndroidPlugins 4 | import io.reactivex.rxjava3.core.Scheduler 5 | import io.reactivex.rxjava3.internal.schedulers.ExecutorScheduler 6 | import io.reactivex.rxjava3.plugins.RxJavaPlugins 7 | import java.util.concurrent.Executor 8 | import org.junit.jupiter.api.extension.AfterEachCallback 9 | import org.junit.jupiter.api.extension.BeforeEachCallback 10 | import org.junit.jupiter.api.extension.ExtensionContext 11 | 12 | /** 13 | * LifeCycle 14 | * 15 | * * BeforeAllCallback 16 | * * BeforeAll 17 | * * BeforeEachCallback 18 | * * BeforeEach 19 | * * BeforeTestExecutionCallback 20 | * * Test 21 | * * AfterTestExecutionCallback 22 | * * AfterEach 23 | * * AfterEachCallback 24 | * * AfterAll 25 | * * AfterAllCallback 26 | */ 27 | class RxImmediateSchedulerExtension : BeforeEachCallback, AfterEachCallback { 28 | 29 | private val immediate = object : Scheduler() { 30 | 31 | override fun createWorker(): Worker { 32 | return ExecutorScheduler.ExecutorWorker(Executor { it.run() }, true, true) 33 | } 34 | } 35 | 36 | // private val immediate = Schedulers.trampoline() 37 | 38 | override fun beforeEach(context: ExtensionContext?) { 39 | RxJavaPlugins.setInitIoSchedulerHandler { immediate } 40 | RxJavaPlugins.setInitComputationSchedulerHandler { immediate } 41 | RxJavaPlugins.setInitNewThreadSchedulerHandler { immediate } 42 | RxJavaPlugins.setInitSingleSchedulerHandler { immediate } 43 | RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediate } 44 | } 45 | 46 | override fun afterEach(context: ExtensionContext?) { 47 | RxJavaPlugins.reset() 48 | RxAndroidPlugins.reset() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/extension/TestCoroutineExtension.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.test_utils.extension 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.test.TestCoroutineDispatcher 5 | import kotlinx.coroutines.test.TestCoroutineScope 6 | import kotlinx.coroutines.test.resetMain 7 | import kotlinx.coroutines.test.runBlockingTest 8 | import kotlinx.coroutines.test.setMain 9 | import org.junit.jupiter.api.extension.AfterEachCallback 10 | import org.junit.jupiter.api.extension.BeforeEachCallback 11 | import org.junit.jupiter.api.extension.ExtensionContext 12 | 13 | /** 14 | * LifeCycle 15 | * 16 | * * BeforeAllCallback 17 | * * BeforeAll 18 | * * BeforeEachCallback 19 | * * BeforeEach 20 | * * BeforeTestExecutionCallback 21 | * * Test 22 | * * AfterTestExecutionCallback 23 | * * AfterEach 24 | * * AfterEachCallback 25 | * * AfterAll 26 | * * AfterAllCallback 27 | */ 28 | class TestCoroutineExtension : BeforeEachCallback, AfterEachCallback { 29 | 30 | private val testCoroutineDispatcher = TestCoroutineDispatcher() 31 | 32 | val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher) 33 | 34 | override fun beforeEach(context: ExtensionContext?) { 35 | println("🚙 TestCoroutineExtension beforeEach()") 36 | Dispatchers.setMain(testCoroutineDispatcher) 37 | } 38 | 39 | override fun afterEach(context: ExtensionContext?) { 40 | 41 | println("🚗 TestCoroutineExtension afterEach()") 42 | 43 | Dispatchers.resetMain() 44 | try { 45 | testCoroutineScope.cleanupTestCoroutines() 46 | } catch (exception: Exception) { 47 | exception.printStackTrace() 48 | } 49 | } 50 | 51 | fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = 52 | testCoroutineScope.runBlockingTest { block() } 53 | } 54 | -------------------------------------------------------------------------------- /libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/rule/MockWebServerRule.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.test_utils.rule 2 | 3 | import okhttp3.mockwebserver.MockWebServer 4 | import org.junit.rules.TestRule 5 | import org.junit.runner.Description 6 | import org.junit.runners.model.Statement 7 | 8 | /** 9 | * Test rule for JUnit4 to invoke actions which are 10 | * start [MockWebServer], 11 | * run the test , 12 | * and shut [MockWebServer] down after the test is run. 13 | */ 14 | class MockWebServerRule : TestRule { 15 | 16 | val mockWebServer = MockWebServer() 17 | 18 | override fun apply( 19 | base: Statement, 20 | description: Description 21 | ): Statement { 22 | 23 | return object : Statement() { 24 | 25 | @Throws(Throwable::class) 26 | override fun evaluate() { 27 | mockWebServer.start() 28 | base.evaluate() 29 | mockWebServer.shutdown() 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/rule/RxImmediateSchedulerRule.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.test_utils.rule 2 | 3 | import io.reactivex.rxjava3.android.plugins.RxAndroidPlugins 4 | import io.reactivex.rxjava3.core.Scheduler 5 | import io.reactivex.rxjava3.internal.schedulers.ExecutorScheduler 6 | import io.reactivex.rxjava3.plugins.RxJavaPlugins 7 | import java.util.concurrent.Executor 8 | import org.junit.rules.TestRule 9 | import org.junit.runner.Description 10 | import org.junit.runners.model.Statement 11 | 12 | class RxImmediateSchedulerRule : TestRule { 13 | 14 | private val immediate = object : Scheduler() { 15 | 16 | override fun createWorker(): Worker { 17 | return ExecutorScheduler.ExecutorWorker(Executor { it.run() }, true, true) 18 | } 19 | } 20 | 21 | // private val immediate = Schedulers.trampoline() 22 | 23 | override fun apply(base: Statement, description: Description): Statement { 24 | return object : Statement() { 25 | @Throws(Throwable::class) 26 | override fun evaluate() { 27 | RxJavaPlugins.setInitIoSchedulerHandler { immediate } 28 | RxJavaPlugins.setInitComputationSchedulerHandler { immediate } 29 | RxJavaPlugins.setInitNewThreadSchedulerHandler { immediate } 30 | RxJavaPlugins.setInitSingleSchedulerHandler { immediate } 31 | RxAndroidPlugins.setInitMainThreadSchedulerHandler { immediate } 32 | 33 | try { 34 | base.evaluate() 35 | } finally { 36 | RxJavaPlugins.reset() 37 | RxAndroidPlugins.reset() 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/rule/TestCoroutineRule.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.test_utils.rule 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.ExperimentalCoroutinesApi 5 | import kotlinx.coroutines.test.TestCoroutineDispatcher 6 | import kotlinx.coroutines.test.TestCoroutineScope 7 | import kotlinx.coroutines.test.resetMain 8 | import kotlinx.coroutines.test.runBlockingTest 9 | import kotlinx.coroutines.test.setMain 10 | import org.junit.rules.TestRule 11 | import org.junit.runner.Description 12 | import org.junit.runners.model.Statement 13 | 14 | @ExperimentalCoroutinesApi 15 | class TestCoroutineRule : TestRule { 16 | 17 | private val testCoroutineDispatcher = TestCoroutineDispatcher() 18 | 19 | val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher) 20 | 21 | override fun apply(base: Statement, description: Description?) = object : Statement() { 22 | 23 | @Throws(Throwable::class) 24 | override fun evaluate() { 25 | 26 | Dispatchers.setMain(testCoroutineDispatcher) 27 | 28 | base.evaluate() 29 | 30 | Dispatchers.resetMain() 31 | try { 32 | testCoroutineScope.cleanupTestCoroutines() 33 | } catch (exception: Exception) { 34 | exception.printStackTrace() 35 | } 36 | } 37 | } 38 | 39 | fun runBlockingTest(block: suspend TestCoroutineScope.() -> Unit) = 40 | testCoroutineScope.runBlockingTest { block() } 41 | } 42 | -------------------------------------------------------------------------------- /libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/util/LiveDataTestUtil.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.test_utils.util 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.Observer 5 | import java.util.concurrent.CountDownLatch 6 | import java.util.concurrent.TimeUnit 7 | import java.util.concurrent.TimeoutException 8 | 9 | /** 10 | * Gets the value of a [LiveData] or waits for it to have one, with a timeout. 11 | * 12 | * Use this extension from host-side (JVM) tests. It's recommended to use it alongside 13 | * `InstantTaskExecutorRule` or a similar mechanism to execute tasks synchronously. 14 | */ 15 | fun LiveData.getOrAwaitValue( 16 | time: Long = 2, 17 | timeUnit: TimeUnit = TimeUnit.SECONDS, 18 | afterObserve: () -> Unit = {} 19 | ): T { 20 | 21 | var data: T? = null 22 | val latch = CountDownLatch(1) 23 | 24 | val observer = object : Observer { 25 | override fun onChanged(o: T?) { 26 | data = o 27 | latch.countDown() 28 | this@getOrAwaitValue.removeObserver(this) 29 | } 30 | } 31 | 32 | this.observeForever(observer) 33 | 34 | afterObserve.invoke() 35 | 36 | // Don't wait indefinitely if the LiveData is not set. 37 | if (!latch.await(time, timeUnit)) { 38 | this.removeObserver(observer) 39 | throw TimeoutException("LiveData value was never set.") 40 | } 41 | 42 | @Suppress("UNCHECKED_CAST") 43 | return data as T 44 | } 45 | -------------------------------------------------------------------------------- /libraries/test-utils/src/main/java/com/smarttoolfactory/test_utils/util/ReadResourceUtil.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.test_utils.util 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import com.google.gson.internal.LinkedTreeMap 6 | import com.google.gson.reflect.TypeToken 7 | 8 | /** 9 | * Use this method to get json files as string from resources folder to use in tests. 10 | */ 11 | fun getResourceAsText(path: String): String { 12 | return object {}.javaClass.classLoader!!.getResource(path)!!.readText() 13 | } 14 | 15 | inline fun Gson.fromJsonWithType(json: String): T? = 16 | fromJson(json, object : TypeToken() {}.type) 17 | 18 | /** 19 | * 20 | * Convert from json to item with type T 21 | * 22 | * * This function returns for some items as [LinkedTreeMap] 23 | */ 24 | inline fun convertToObjectFromJson(json: String): T? { 25 | return Gson().fromJsonWithType(json) 26 | } 27 | 28 | /** 29 | * 30 | * Convert from json to [List] of items with type T 31 | * 32 | * * This function returns for some items as [LinkedTreeMap] 33 | */ 34 | inline fun fromJsonToListOf(json: String): List { 35 | return GsonBuilder().create().fromJson(json, Array::class.java).asList() 36 | } 37 | 38 | fun Gson.mapFromLinkedTreeMap(map: Map?, type: Class): T? { 39 | if (map == null) return null 40 | 41 | val json = toJson(map) 42 | return fromJson(json, type) 43 | } 44 | 45 | inline fun convertFromJsonToListOf(json: String): List? { 46 | 47 | val gson = GsonBuilder().create() 48 | 49 | val itemList = fromJsonToListOf(json) 50 | 51 | if (itemList.first() !is LinkedTreeMap<*, *>) 52 | return itemList 53 | 54 | // Must use map here because the result is a list of LinkedTreeMaps 55 | val list: ArrayList>? = gson.fromJsonWithType(json) 56 | // handle type erasure 57 | 58 | return list?.mapNotNull { 59 | gson.mapFromLinkedTreeMap(it, T::class.java) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /libraries/test-utils/src/test/java/com/smarttoolfactory/test_utils/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.smarttoolfactory.test_utils 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 | -------------------------------------------------------------------------------- /screenshots/account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/screenshots/account.png -------------------------------------------------------------------------------- /screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/screenshots/dashboard.png -------------------------------------------------------------------------------- /screenshots/notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/screenshots/notifications.png -------------------------------------------------------------------------------- /screenshots/property_flow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/screenshots/property_flow.gif -------------------------------------------------------------------------------- /screenshots/property_overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/screenshots/property_overview.gif -------------------------------------------------------------------------------- /screenshots/property_pagination.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/screenshots/property_pagination.gif -------------------------------------------------------------------------------- /screenshots/property_rxjava3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SmartToolFactory/PropertyFindAR/5d2b510a8d1c489a5520b1fff6a143552e50ca79/screenshots/property_rxjava3.gif -------------------------------------------------------------------------------- /scripts/git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | echo "Running static analysis..." 5 | 6 | 7 | # Inspect code using KtLint, and Detekt 8 | 9 | # Run KtLint only 10 | #./gradlew app:ktlintCheck --daemon 11 | 12 | # Format code using KtLint, then run Detekt and KtLint static analysis 13 | ./gradlew app:ktlintFormat app:detekt app:ktlintCheck --daemon 14 | 15 | status=$? 16 | 17 | 18 | if [ "$status" = 0 ] ; then 19 | 20 | echo "Static analysis found no problems." 21 | 22 | exit 0 23 | 24 | else 25 | 26 | echo 1>&2 "Static analysis found violations it could not fix." 27 | 28 | exit 1 29 | 30 | fi 31 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include( 2 | ":app", 3 | ":libraries:test-utils", 4 | ":libraries:data", 5 | ":libraries:domain", 6 | ":libraries:core", 7 | ":features:home", 8 | ":features:property_detail", 9 | ":features:dashboard", 10 | ":features:notification", 11 | ":features:account" 12 | ) 13 | --------------------------------------------------------------------------------