├── .github ├── pull_request_template.md └── workflows │ └── android_build.yml ├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ramadan │ │ └── takeaway │ │ └── data │ │ └── db │ │ └── AppDatabaseTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── data.json │ ├── ic_launcher_debug-playstore.png │ ├── java │ │ └── com │ │ │ └── ramadan │ │ │ └── takeaway │ │ │ ├── App.kt │ │ │ ├── data │ │ │ ├── db │ │ │ │ ├── AppDatabase.kt │ │ │ │ ├── RestaurantEntity.kt │ │ │ │ └── RestaurantsDAO.kt │ │ │ ├── mapper │ │ │ │ ├── RestaurantEntityMapper.kt │ │ │ │ └── RestaurantMapper.kt │ │ │ ├── model │ │ │ │ └── DataModels.kt │ │ │ └── repository │ │ │ │ ├── RestaurantsRepository.kt │ │ │ │ └── RestaurantsRepositoryImpl.kt │ │ │ ├── di │ │ │ ├── DatabaseModule.kt │ │ │ └── RepositoryModule.kt │ │ │ ├── domain │ │ │ ├── interactor │ │ │ │ ├── FavoriteRestaurantInteractor.kt │ │ │ │ ├── GetRestaurantsInteractor.kt │ │ │ │ ├── UnFavoriteRestaurantInteractor.kt │ │ │ │ └── Usecase.kt │ │ │ └── model │ │ │ │ └── Restaurant.kt │ │ │ ├── ui │ │ │ ├── HomeFragment.kt │ │ │ ├── MainActivity.kt │ │ │ ├── adapter │ │ │ │ ├── RestaurantSortedListImpl.kt │ │ │ │ └── RestaurantsAdapter.kt │ │ │ ├── mapper │ │ │ │ └── RestaurantModelMapper.kt │ │ │ ├── model │ │ │ │ └── RestaurantModel.kt │ │ │ ├── sort │ │ │ │ ├── SortingOptionsHandler.kt │ │ │ │ └── wraper │ │ │ │ │ └── SortedListComparatorWrapper.kt │ │ │ └── viewmodel │ │ │ │ └── RestaurantsViewModel.kt │ │ │ └── util │ │ │ ├── DataState.kt │ │ │ ├── EntityMapper.kt │ │ │ ├── OpeningState.kt │ │ │ ├── RestaurantDiffUtils.kt │ │ │ └── SortingKeys.kt │ └── res │ │ ├── drawable-v24 │ │ ├── ic_launcher_debug_foreground.xml │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_favorite_add.xml │ │ ├── ic_favorite_remove.xml │ │ ├── ic_favorites.xml │ │ ├── ic_filter.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_search.xml │ │ ├── ic_settings_24.xml │ │ ├── ic_sort.xml │ │ ├── ic_star.xml │ │ ├── ic_text_found.xml │ │ └── selector_favorite.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_home.xml │ │ └── item_restaurant_view.xml │ │ ├── menu │ │ ├── menu_search.xml │ │ └── menu_sort_actions.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ ├── ic_launcher_debug.xml │ │ ├── ic_launcher_debug_round.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_debug.png │ │ ├── ic_launcher_debug_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_debug.png │ │ ├── ic_launcher_debug_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_debug.png │ │ ├── ic_launcher_debug_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_debug.png │ │ ├── ic_launcher_debug_round.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_debug.png │ │ ├── ic_launcher_debug_round.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ └── nav_graph.xml │ │ ├── values-night │ │ └── colors.xml │ │ ├── values-v21 │ │ └── theme.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_debug_background.xml │ │ ├── strings.xml │ │ └── theme.xml │ │ └── xml │ │ └── searchable.xml │ └── test │ ├── java │ └── com │ │ └── ramadan │ │ └── takeaway │ │ └── ui │ │ ├── sort │ │ └── SortingOptionsHandlerTest.kt │ │ └── viewmodel │ │ └── RestaurantsViewModelTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── build.gradle ├── buildSrc ├── build.gradle.kts ├── build │ ├── classes │ │ └── kotlin │ │ │ └── main │ │ │ ├── Android.class │ │ │ ├── BuildPlugins.class │ │ │ ├── Libs.class │ │ │ ├── META-INF │ │ │ └── buildSrc.kotlin_module │ │ │ ├── TestLibs.class │ │ │ └── Versions.class │ ├── kotlin │ │ ├── buildSrcjar-classes.txt │ │ └── compileKotlin │ │ │ ├── build-history.bin │ │ │ ├── caches-jvm │ │ │ ├── inputs │ │ │ │ ├── source-to-output.tab │ │ │ │ ├── source-to-output.tab.keystream │ │ │ │ ├── source-to-output.tab.keystream.len │ │ │ │ ├── source-to-output.tab.len │ │ │ │ ├── source-to-output.tab.values.at │ │ │ │ ├── source-to-output.tab_i │ │ │ │ └── source-to-output.tab_i.len │ │ │ ├── jvm │ │ │ │ └── kotlin │ │ │ │ │ ├── class-fq-name-to-source.tab │ │ │ │ │ ├── class-fq-name-to-source.tab.keystream │ │ │ │ │ ├── class-fq-name-to-source.tab.keystream.len │ │ │ │ │ ├── class-fq-name-to-source.tab.len │ │ │ │ │ ├── class-fq-name-to-source.tab.values.at │ │ │ │ │ ├── class-fq-name-to-source.tab_i │ │ │ │ │ ├── class-fq-name-to-source.tab_i.len │ │ │ │ │ ├── internal-name-to-source.tab │ │ │ │ │ ├── internal-name-to-source.tab.keystream │ │ │ │ │ ├── internal-name-to-source.tab.keystream.len │ │ │ │ │ ├── internal-name-to-source.tab.len │ │ │ │ │ ├── internal-name-to-source.tab.values.at │ │ │ │ │ ├── internal-name-to-source.tab_i │ │ │ │ │ ├── internal-name-to-source.tab_i.len │ │ │ │ │ ├── proto.tab │ │ │ │ │ ├── proto.tab.keystream │ │ │ │ │ ├── proto.tab.keystream.len │ │ │ │ │ ├── proto.tab.len │ │ │ │ │ ├── proto.tab.values.at │ │ │ │ │ ├── proto.tab_i │ │ │ │ │ ├── proto.tab_i.len │ │ │ │ │ ├── source-to-classes.tab │ │ │ │ │ ├── source-to-classes.tab.keystream │ │ │ │ │ ├── source-to-classes.tab.keystream.len │ │ │ │ │ ├── source-to-classes.tab.len │ │ │ │ │ ├── source-to-classes.tab.values.at │ │ │ │ │ ├── source-to-classes.tab_i │ │ │ │ │ └── source-to-classes.tab_i.len │ │ │ └── lookups │ │ │ │ ├── counters.tab │ │ │ │ ├── file-to-id.tab │ │ │ │ ├── file-to-id.tab.keystream │ │ │ │ ├── file-to-id.tab.keystream.len │ │ │ │ ├── file-to-id.tab.len │ │ │ │ ├── file-to-id.tab.values.at │ │ │ │ ├── file-to-id.tab_i │ │ │ │ ├── file-to-id.tab_i.len │ │ │ │ ├── id-to-file.tab │ │ │ │ ├── id-to-file.tab.keystream │ │ │ │ ├── id-to-file.tab.keystream.len │ │ │ │ ├── id-to-file.tab.len │ │ │ │ ├── id-to-file.tab.values.at │ │ │ │ ├── lookups.tab │ │ │ │ ├── lookups.tab.keystream │ │ │ │ ├── lookups.tab.keystream.len │ │ │ │ ├── lookups.tab.len │ │ │ │ ├── lookups.tab.values.at │ │ │ │ ├── lookups.tab_i │ │ │ │ └── lookups.tab_i.len │ │ │ └── last-build.bin │ ├── libs │ │ └── buildSrc.jar │ ├── pluginUnderTestMetadata │ │ └── plugin-under-test-metadata.properties │ ├── reports │ │ └── plugin-development │ │ │ └── validation-report.txt │ ├── source-roots │ │ └── buildSrc │ │ │ └── source-roots.txt │ └── tmp │ │ └── jar │ │ └── MANIFEST.MF └── src │ └── main │ └── java │ └── Dependencies.kt ├── easy_adb.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── settings ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ramadan │ │ └── settings │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── ramadan │ │ │ └── settings │ │ │ ├── SettingsFragment.kt │ │ │ └── SettingsViewModel.kt │ └── res │ │ ├── layout │ │ └── fragment_settings.xml │ │ ├── navigation │ │ └── settings_nav.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── ramadan │ └── settings │ └── ExampleUnitTest.kt ├── test-utils ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── ramadan │ └── test_utils │ ├── MockitoUtils.kt │ └── RxSchedulerRule.kt └── theme ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── ramadan │ └── theme │ └── ExampleInstrumentedTest.kt ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── ramadan │ │ └── theme │ │ └── ui │ │ └── ThemeChangeDialog.kt └── res │ ├── layout │ └── dialog_theme.xml │ ├── navigation │ └── nav_theme.xml │ └── values │ └── strings.xml └── test └── java └── com └── ramadan └── theme └── ExampleUnitTest.kt /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Related Issue 2 | - issue goes here 3 | 4 | # Proposed Changes 5 | - change 1 6 | - change 2 7 | 8 | # Additional Info 9 | - any additiona info or context 10 | 11 | # Check List 12 | 13 | - [ ] Tests 14 | - [ ] Translation 15 | - [ ] Documentation 16 | 17 | # Screenshots 18 | Original | Updated 19 | :---------------------------------:|:--------------------------------------: 20 | ** original screenshots ** | ** Updated screenshot ** 21 | -------------------------------------------------------------------------------- /.github/workflows/android_build.yml: -------------------------------------------------------------------------------- 1 | name : Android Build 2 | on : pull_request 3 | 4 | jobs: 5 | build: 6 | runs-on : ubuntu-latest 7 | steps: 8 | - uses : actions/checkout@v1 9 | 10 | - name: Setup JDK 11 | uses: actions/setup-java@v1 12 | with: 13 | java-version: 1.8 14 | 15 | - name: Run Tests 16 | run: ./gradlew test 17 | 18 | - name: Build Project 19 | run: ./gradlew assemble -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | /.idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Challenge 1 3 | 4 | The goal of this challenge is to implement a sample project, where you can see a restaurant list. 5 | You will able to sort the restaurant list based on it’s current openings state, you can favourite a restaurant and 6 | you can select a sort value to further sort the list. Finally you would like to see you add the option to filter the restaurant list, based on a restaurant’s name. 7 | In the assets you can find a JSON file (data.json), this file contains all the necessary data to complete this challenge. Parse the JSON file and use it for the visualization and sorting of the list. 8 | Use the following priority of the sorting (from the highest to the lowest priority): 9 | 10 | 1. Favourites​: Favourite restaurants are at the top of the list, your current favourite restaurants are stored locally on the phone. 11 | 2. Openings state​: Restaurant is either open (top), you can order ahead (middle) or a restaurant is currently closed (bottom). (Values available in data.json) 12 | 3. Sort options​: Always one sort option is chosen and this can be best match, newest, rating average, distance, popularity, average product price, delivery costs or the minimum cost. (Values available in data.json) 13 | 4. Filtering​: It’s up to you how you how you want to search by restaurant name. 14 | 15 | 16 | ## How to run the project 17 | 18 | 1. Download the project 19 | 2. Import the project to Android Studio Tool 20 | 3. Run the project 21 | 4. Check the Emulator or your real device 22 | 23 | ## How to run the unit and instrumentation tests 24 | 25 | 1. Make sure your emulator is up running (for instrumentation test only) 26 | 2. OPen the terminal from Android Studio 27 | 3- type the following command in terminal 28 | 29 | ./gradlew test connectedAndroidTest 30 | 31 | ## Run Ktlint 32 | 33 | 1. to check the formatting. Run the following command in terminal 34 | ./gradlew ktlintCheck 35 | 2. to format the code. Run the following command in terminal 36 | ./gradlew ktlintFormat 37 | 38 | ## The App Architecture 39 | 40 | 1. Clean Architecture based on the MVVM Architecture pattern with Interactors 41 | 42 | ## The Template Extras 43 | 44 | 1. The App has two build types debug and release with two different app icons and app names 45 | 2. support dark theme 46 | 47 | ## Libs 48 | 49 | 1. Hilt for dependency Injection 50 | 2. RxJava for Handling Threading , powerful operators and Reactive Programming 51 | 3. Room for store data locally 52 | 4. Architecture Components like ViewModel and LiveData 53 | 5. Junit for Assertions 54 | 6. Mockito for mocking objects 55 | 7. Material Design components 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'dagger.hilt.android.plugin' 6 | 7 | junitJacoco { 8 | jacocoVersion = '0.8.4' // type String 9 | includeNoLocationClasses = false // type boolean 10 | includeInstrumentationCoverageInMergedReport = false // type boolean 11 | } 12 | 13 | android { 14 | compileSdkVersion Android.compileSDK 15 | 16 | defaultConfig { 17 | applicationId Android.applicationId 18 | minSdkVersion Android.minSDK 19 | targetSdkVersion Android.targetSDK 20 | versionCode Android.versionCode 21 | versionName Android.versionName 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | multiDexEnabled true 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled true 29 | shrinkResources true 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | manifestPlaceholders = [ 32 | appName: '@string/app_name', 33 | appIcon: '@mipmap/ic_launcher' 34 | ] 35 | } 36 | 37 | debug{ 38 | applicationIdSuffix '.debug' 39 | versionNameSuffix '-debug' 40 | manifestPlaceholders = [ 41 | appName: '@string/app_name_debug', 42 | appIcon: '@mipmap/ic_launcher_debug' 43 | ] 44 | } 45 | } 46 | 47 | kotlinOptions { 48 | jvmTarget = "1.8" 49 | } 50 | 51 | compileOptions { 52 | sourceCompatibility JavaVersion.VERSION_1_8 53 | targetCompatibility JavaVersion.VERSION_1_8 54 | } 55 | } 56 | 57 | dependencies { 58 | implementation fileTree(dir: "libs", include: ["*.jar"]) 59 | //Libs 60 | implementation Libs.kotlinStdLib 61 | implementation Libs.appCompat 62 | implementation Libs.coreExt 63 | implementation Libs.material 64 | implementation Libs.recyclerview 65 | implementation Libs.cardview 66 | implementation Libs.constraintLayout 67 | implementation Libs.rxVersion 68 | implementation Libs.rxAndroid 69 | implementation Libs.gson 70 | implementation Libs.viewModel 71 | implementation Libs.liveData 72 | implementation Libs.multidex 73 | implementation Libs.fragmentKtx 74 | implementation Libs.hiltAndroid 75 | kapt Libs.hiltAndroidCompiler 76 | implementation Libs.room 77 | kapt Libs.roomCompiler 78 | implementation Libs.rxRoom 79 | implementation Libs.navigationKtx 80 | implementation Libs.navigationUI 81 | implementation project(":settings") 82 | 83 | 84 | 85 | 86 | //Testing Libs 87 | testImplementation TestLibs.junit 88 | testImplementation TestLibs.archCoreTesting 89 | androidTestImplementation TestLibs.testRunner 90 | androidTestImplementation TestLibs.rules 91 | androidTestImplementation TestLibs.truth 92 | androidTestImplementation TestLibs.junitExt 93 | testImplementation TestLibs.mockito 94 | androidTestImplementation TestLibs.mockitoAndroid 95 | 96 | testImplementation(project(":test-utils")) 97 | } -------------------------------------------------------------------------------- /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/ramadan/takeaway/data/db/AppDatabaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.data.db 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import androidx.test.core.app.ApplicationProvider 6 | import androidx.test.ext.junit.runners.AndroidJUnit4 7 | import org.junit.After 8 | import org.junit.Before 9 | import org.junit.Test 10 | import org.junit.runner.RunWith 11 | import java.io.IOException 12 | 13 | @RunWith(AndroidJUnit4::class) 14 | class AppDatabaseTest { 15 | private lateinit var restaurantsDAO: RestaurantsDAO 16 | private lateinit var db: AppDatabase 17 | 18 | @Before 19 | fun createDb() { 20 | val context = ApplicationProvider.getApplicationContext() 21 | db = Room.inMemoryDatabaseBuilder( 22 | context, AppDatabase::class.java 23 | ).build() 24 | restaurantsDAO = db.restaurantsDao() 25 | } 26 | 27 | @After 28 | @Throws(IOException::class) 29 | fun closeDb() { 30 | db.close() 31 | } 32 | 33 | @Test 34 | @Throws(Exception::class) 35 | fun favoriteRestaurantAndReadInList() { 36 | val rest = RestaurantEntity(name = "SeaFood", isFavorite = true) 37 | restaurantsDAO.favorite(rest).blockingAwait() 38 | restaurantsDAO.getFavorites().test().assertValue { 39 | list -> 40 | list.isNotEmpty() 41 | } 42 | } 43 | 44 | @Test 45 | @Throws(Exception::class) 46 | fun unFavoriteRestaurantAndReadInList() { 47 | val rest = RestaurantEntity(name = "SeaFood", isFavorite = true) 48 | restaurantsDAO.favorite(rest).blockingAwait() 49 | rest.copy(isFavorite = false) 50 | restaurantsDAO.unFavorite(rest).blockingAwait() 51 | restaurantsDAO.getFavorites().test().assertValue { 52 | list -> 53 | list.isEmpty() 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/assets/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "restaurants": [{ 3 | "name": "Tanoshii Sushi", 4 | "status": "open", 5 | "sortingValues": { 6 | "bestMatch": 0.0, 7 | "newest": 96.0, 8 | "ratingAverage": 4.5, 9 | "distance": 1190, 10 | "popularity": 17.0, 11 | "averageProductPrice": 1536, 12 | "deliveryCosts": 200, 13 | "minCost": 1000 14 | } 15 | }, { 16 | "name": "Tandoori Express", 17 | "status": "closed", 18 | "sortingValues": { 19 | "bestMatch": 1.0, 20 | "newest": 266.0, 21 | "ratingAverage": 4.5, 22 | "distance": 2308, 23 | "popularity": 123.0, 24 | "averageProductPrice": 1146, 25 | "deliveryCosts": 150, 26 | "minCost": 1300 27 | } 28 | }, { 29 | "name": "Royal Thai", 30 | "status": "order ahead", 31 | "sortingValues": { 32 | "bestMatch": 2.0, 33 | "newest": 133.0, 34 | "ratingAverage": 4.5, 35 | "distance": 2639, 36 | "popularity": 44.0, 37 | "averageProductPrice": 1492, 38 | "deliveryCosts": 150, 39 | "minCost": 2500 40 | } 41 | }, { 42 | "name": "Sushi One", 43 | "status": "open", 44 | "sortingValues": { 45 | "bestMatch": 3.0, 46 | "newest": 238.0, 47 | "ratingAverage": 4.0, 48 | "distance": 1618, 49 | "popularity": 23.0, 50 | "averageProductPrice": 1285, 51 | "deliveryCosts": 0, 52 | "minCost": 1200 53 | } 54 | }, { 55 | "name": "Roti Shop", 56 | "status": "open", 57 | "sortingValues": { 58 | "bestMatch": 4.0, 59 | "newest": 247.0, 60 | "ratingAverage": 4.5, 61 | "distance": 2308, 62 | "popularity": 81.0, 63 | "averageProductPrice": 915, 64 | "deliveryCosts": 0, 65 | "minCost": 2000 66 | } 67 | }, { 68 | "name": "Aarti 2", 69 | "status": "open", 70 | "sortingValues": { 71 | "bestMatch": 5.0, 72 | "newest": 153.0, 73 | "ratingAverage": 4.5, 74 | "distance": 1605, 75 | "popularity": 44.0, 76 | "averageProductPrice": 922, 77 | "deliveryCosts": 250, 78 | "minCost": 500 79 | } 80 | }, { 81 | "name": "Pizza Heart", 82 | "status": "order ahead", 83 | "sortingValues": { 84 | "bestMatch": 6.0, 85 | "newest": 118.0, 86 | "ratingAverage": 4.0, 87 | "distance": 2453, 88 | "popularity": 9.0, 89 | "averageProductPrice": 1103, 90 | "deliveryCosts": 150, 91 | "minCost": 1500 92 | } 93 | }, { 94 | "name": "Mama Mia", 95 | "status": "order ahead", 96 | "sortingValues": { 97 | "bestMatch": 7.0, 98 | "newest": 250.0, 99 | "ratingAverage": 4.0, 100 | "distance": 1396, 101 | "popularity": 6.0, 102 | "averageProductPrice": 912, 103 | "deliveryCosts": 0, 104 | "minCost": 1000 105 | } 106 | }, { 107 | "name": "Feelfood", 108 | "status": "order ahead", 109 | "sortingValues": { 110 | "bestMatch": 8.0, 111 | "newest": 163.0, 112 | "ratingAverage": 4.5, 113 | "distance": 2732, 114 | "popularity": 31.0, 115 | "averageProductPrice": 902, 116 | "deliveryCosts": 150, 117 | "minCost": 1500 118 | } 119 | }, { 120 | "name": "Daily Sushi", 121 | "status": "closed", 122 | "sortingValues": { 123 | "bestMatch": 9.0, 124 | "newest": 221.0, 125 | "ratingAverage": 4.0, 126 | "distance": 1911, 127 | "popularity": 6.0, 128 | "averageProductPrice": 1327, 129 | "deliveryCosts": 200, 130 | "minCost": 1000 131 | } 132 | }, { 133 | "name": "Pamukkale", 134 | "status": "closed", 135 | "sortingValues": { 136 | "bestMatch": 10.0, 137 | "newest": 201.0, 138 | "ratingAverage": 4.0, 139 | "distance": 2353, 140 | "popularity": 25.0, 141 | "averageProductPrice": 968, 142 | "deliveryCosts": 0, 143 | "minCost": 2000 144 | } 145 | }, { 146 | "name": "Indian Kitchen", 147 | "status": "open", 148 | "sortingValues": { 149 | "bestMatch": 11.0, 150 | "newest": 272.0, 151 | "ratingAverage": 4.5, 152 | "distance": 2308, 153 | "popularity": 5.0, 154 | "averageProductPrice": 1189, 155 | "deliveryCosts": 150, 156 | "minCost": 1300 157 | } 158 | }, { 159 | "name": "CIRO 1939", 160 | "status": "open", 161 | "sortingValues": { 162 | "bestMatch": 12.0, 163 | "newest": 231.0, 164 | "ratingAverage": 4.5, 165 | "distance": 3957, 166 | "popularity": 79.0, 167 | "averageProductPrice": 1762, 168 | "deliveryCosts": 99, 169 | "minCost": 1300 170 | } 171 | }, { 172 | "name": "Zenzai Sushi", 173 | "status": "closed", 174 | "sortingValues": { 175 | "bestMatch": 13.0, 176 | "newest": 155.0, 177 | "ratingAverage": 4.0, 178 | "distance": 2911, 179 | "popularity": 36.0, 180 | "averageProductPrice": 1579, 181 | "deliveryCosts": 0, 182 | "minCost": 2000 183 | } 184 | }, { 185 | "name": "Fes Patisserie", 186 | "status": "order ahead", 187 | "sortingValues": { 188 | "bestMatch": 14.0, 189 | "newest": 77.0, 190 | "ratingAverage": 4.0, 191 | "distance": 2302, 192 | "popularity": 3.0, 193 | "averageProductPrice": 1214, 194 | "deliveryCosts": 150, 195 | "minCost": 1250 196 | } 197 | }, { 198 | "name": "Yvonne's Vispaleis", 199 | "status": "order ahead", 200 | "sortingValues": { 201 | "bestMatch": 15.0, 202 | "newest": 150.0, 203 | "ratingAverage": 5.0, 204 | "distance": 2909, 205 | "popularity": 3.0, 206 | "averageProductPrice": 2557, 207 | "deliveryCosts": 150, 208 | "minCost": 1750 209 | } 210 | }, { 211 | "name": "De Amsterdamsche Tram", 212 | "status": "open", 213 | "sortingValues": { 214 | "bestMatch": 304.0, 215 | "newest": 131.0, 216 | "ratingAverage": 0.0, 217 | "distance": 2792, 218 | "popularity": 0.0, 219 | "averageProductPrice": 892, 220 | "deliveryCosts": 0, 221 | "minCost": 0 222 | } 223 | }, { 224 | "name": "Lale Restaurant & Snackbar", 225 | "status": "order ahead", 226 | "sortingValues": { 227 | "bestMatch": 305.0, 228 | "newest": 73.0, 229 | "ratingAverage": 0.0, 230 | "distance": 2880, 231 | "popularity": 0.0, 232 | "averageProductPrice": 838, 233 | "deliveryCosts": 0, 234 | "minCost": 0 235 | } 236 | }, { 237 | "name": "Lunchpakketdienst", 238 | "status": "open", 239 | "sortingValues": { 240 | "bestMatch": 306.0, 241 | "newest": 259.0, 242 | "ratingAverage": 3.5, 243 | "distance": 14201, 244 | "popularity": 0.0, 245 | "averageProductPrice": 4465, 246 | "deliveryCosts": 500, 247 | "minCost": 5000 248 | } 249 | }] 250 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher_debug-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/ic_launcher_debug-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/App.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class App : Application() { 8 | override fun onCreate() { 9 | super.onCreate() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/data/db/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.data.db 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | 6 | @Database( 7 | entities = [RestaurantEntity::class], 8 | version = 1, 9 | exportSchema = false 10 | ) 11 | abstract class AppDatabase : RoomDatabase() { 12 | companion object { 13 | const val DATABASE_NAME = "restaurants-db" 14 | } 15 | abstract fun restaurantsDao(): RestaurantsDAO 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/data/db/RestaurantEntity.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.data.db 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "restaurants") 8 | data class RestaurantEntity( 9 | @PrimaryKey 10 | @ColumnInfo(name = "rest_name") 11 | val name: String, 12 | @ColumnInfo(name = "is_favorite") 13 | var isFavorite: Boolean 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/data/db/RestaurantsDAO.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.data.db 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 io.reactivex.Completable 9 | import io.reactivex.Single 10 | 11 | @Dao 12 | interface RestaurantsDAO { 13 | /** 14 | * Favorites a restaurant by saving it to user's favorites. 15 | */ 16 | @Insert(onConflict = OnConflictStrategy.REPLACE) 17 | fun favorite(entity: RestaurantEntity): Completable 18 | 19 | /** 20 | * UnFavorite a restaurant by deleting it from user's favorites. 21 | */ 22 | @Delete 23 | fun unFavorite(entity: RestaurantEntity): Completable 24 | 25 | /** 26 | * Returns all user's favorite restaurants. 27 | */ 28 | @Query("SELECT * FROM restaurants") 29 | fun getFavorites(): Single> 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/data/mapper/RestaurantEntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.data.mapper 2 | 3 | import com.ramadan.takeaway.data.db.RestaurantEntity 4 | import com.ramadan.takeaway.domain.model.Restaurant 5 | import com.ramadan.takeaway.util.EntityMapper 6 | 7 | object RestaurantEntityMapper : EntityMapper { 8 | override fun mapFromEntity(entity: RestaurantEntity): Restaurant { 9 | return Restaurant( 10 | name = entity.name, 11 | isFavorite = entity.isFavorite 12 | ) 13 | } 14 | 15 | override fun mapToEntity(model: Restaurant): RestaurantEntity { 16 | return RestaurantEntity(name = model.name, isFavorite = model.isFavorite) 17 | } 18 | 19 | fun mapFromEntityList(entities: List): List { 20 | return entities.map { mapFromEntity(it) } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/data/mapper/RestaurantMapper.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.data.mapper 2 | 3 | import com.ramadan.takeaway.data.model.RestaurantItem 4 | import com.ramadan.takeaway.data.model.SortingValues 5 | import com.ramadan.takeaway.domain.model.Restaurant 6 | import com.ramadan.takeaway.util.EntityMapper 7 | 8 | object RestaurantMapper : EntityMapper { 9 | 10 | override fun mapFromEntity(entity: RestaurantItem): Restaurant { 11 | return Restaurant( 12 | name = entity.name, 13 | status = entity.status, 14 | averageProductPrice = entity.sortingValues.averageProductPrice, 15 | bestMatch = entity.sortingValues.bestMatch, 16 | distance = entity.sortingValues.distance, 17 | deliveryCosts = entity.sortingValues.deliveryCosts, 18 | popularity = entity.sortingValues.popularity, 19 | newest = entity.sortingValues.newest, 20 | minCost = entity.sortingValues.minCost, 21 | ratingAverage = entity.sortingValues.ratingAverage 22 | ) 23 | } 24 | 25 | override fun mapToEntity(domainModel: Restaurant): RestaurantItem { 26 | val averageProductPrice = domainModel.averageProductPrice 27 | val bestMatch = domainModel.bestMatch 28 | val distance = domainModel.distance 29 | val deliveryCosts = domainModel.deliveryCosts 30 | val popularity = domainModel.popularity 31 | val newest = domainModel.newest 32 | val minCost = domainModel.minCost 33 | val ratingAverage = domainModel.ratingAverage 34 | 35 | val sortingValues = SortingValues( 36 | averageProductPrice, 37 | bestMatch, 38 | distance, 39 | deliveryCosts, 40 | popularity, 41 | newest, 42 | minCost, 43 | ratingAverage 44 | ) 45 | return RestaurantItem( 46 | name = domainModel.name, 47 | status = domainModel.status, 48 | sortingValues = sortingValues 49 | ) 50 | } 51 | 52 | fun mapFromEntityList(entities: List): List { 53 | return entities.map { mapFromEntity(it) } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/data/model/DataModels.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.data.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class RestaurantsResponse( 7 | @SerializedName("restaurants") 8 | val restaurants: List? 9 | ) 10 | 11 | data class RestaurantItem( 12 | @SerializedName("sortingValues") 13 | val sortingValues: SortingValues, 14 | @SerializedName("name") 15 | val name: String, 16 | @SerializedName("status") 17 | val status: String 18 | ) 19 | 20 | data class SortingValues( 21 | @SerializedName("averageProductPrice") 22 | val averageProductPrice: Double, 23 | @SerializedName("bestMatch") 24 | val bestMatch: Double, 25 | @SerializedName("distance") 26 | val distance: Double, 27 | @SerializedName("deliveryCosts") 28 | val deliveryCosts: Double, 29 | @SerializedName("popularity") 30 | val popularity: Double, 31 | @SerializedName("newest") 32 | val newest: Double, 33 | @SerializedName("minCost") 34 | val minCost: Double, 35 | @SerializedName("ratingAverage") 36 | val ratingAverage: Double 37 | ) 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/data/repository/RestaurantsRepository.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.data.repository 2 | 3 | import com.ramadan.takeaway.domain.model.Restaurant 4 | import io.reactivex.Completable 5 | import io.reactivex.Single 6 | 7 | interface RestaurantsRepository { 8 | 9 | /** 10 | * Return a single which will emits a list of restaurants otherwise an error. 11 | */ 12 | fun getRestaurants(): Single> 13 | 14 | /** 15 | * Return a Completable which complete bookmark operation or error. 16 | * @param restaurant to be bookmarked 17 | */ 18 | fun favoriteRestaurant(restaurant: Restaurant): Completable 19 | 20 | /** 21 | * Return a Completable which complete un-bookmark operation or error. 22 | * @param restaurant to be un-bookmarked 23 | */ 24 | fun unFavoriteRestaurant(restaurant: Restaurant): Completable 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/data/repository/RestaurantsRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.data.repository 2 | 3 | import android.content.Context 4 | import com.google.gson.Gson 5 | import com.ramadan.takeaway.data.db.RestaurantsDAO 6 | import com.ramadan.takeaway.data.mapper.RestaurantEntityMapper 7 | import com.ramadan.takeaway.data.mapper.RestaurantMapper 8 | import com.ramadan.takeaway.data.model.RestaurantsResponse 9 | import com.ramadan.takeaway.domain.model.Restaurant 10 | import io.reactivex.Completable 11 | import io.reactivex.Single 12 | import io.reactivex.functions.BiFunction 13 | import java.io.IOException 14 | import java.nio.charset.Charset 15 | 16 | class RestaurantsRepositoryImpl(private val context: Context, private val dao: RestaurantsDAO) : RestaurantsRepository { 17 | 18 | private fun getRestaurantsFromJsonFile(): Single> { 19 | val response = loadJSONFromAssets(context, "data.json") ?: "{}" 20 | val gson = Gson() 21 | val restaurants = gson.fromJson( 22 | response, 23 | RestaurantsResponse::class.java 24 | ).restaurants 25 | return Single.defer { 26 | Single.just(restaurants?.let { RestaurantMapper.mapFromEntityList(it) }) 27 | } 28 | } 29 | 30 | private fun getFavoritesFromDB(): Single> { 31 | return dao.getFavorites().map { 32 | RestaurantEntityMapper.mapFromEntityList(it) 33 | } 34 | } 35 | 36 | private fun loadJSONFromAssets(context: Context, filePath: String): String? { 37 | var json: String? = null 38 | try { 39 | val inputStream = context.assets.open(filePath) 40 | val size = inputStream.available() 41 | val buffer = ByteArray(size) 42 | inputStream.read(buffer) 43 | inputStream.close() 44 | json = String(buffer, Charset.forName("UTF-8")) 45 | } catch (e: IOException) { 46 | e.printStackTrace() 47 | } 48 | 49 | return json 50 | } 51 | 52 | override fun getRestaurants(): Single> { 53 | return Single.zip( 54 | getRestaurantsFromJsonFile(), 55 | getFavoritesFromDB(), 56 | BiFunction, List, List> { data, favorites -> 57 | 58 | data.map { 59 | // Update favorites. 60 | favorites.map { favorite -> 61 | if (favorite.name == it.name) { 62 | it.isFavorite = true 63 | } 64 | } 65 | it 66 | } 67 | } 68 | ) 69 | } 70 | 71 | override fun favoriteRestaurant(restaurant: Restaurant): Completable { 72 | return dao.favorite(RestaurantEntityMapper.mapToEntity(restaurant)) 73 | } 74 | 75 | override fun unFavoriteRestaurant(restaurant: Restaurant): Completable { 76 | return dao.unFavorite(RestaurantEntityMapper.mapToEntity(restaurant)) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import com.ramadan.takeaway.data.db.AppDatabase 6 | import com.ramadan.takeaway.data.db.RestaurantsDAO 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | object DatabaseModule { 17 | @Singleton 18 | @Provides 19 | fun providesDatabase(@ApplicationContext context: Context): AppDatabase { 20 | return Room.databaseBuilder( 21 | context, 22 | AppDatabase::class.java, 23 | AppDatabase.DATABASE_NAME 24 | ) 25 | .fallbackToDestructiveMigration() 26 | .build() 27 | } 28 | @Singleton 29 | @Provides 30 | fun providesRestaurantDAO(appDatabase: AppDatabase): RestaurantsDAO { 31 | return appDatabase.restaurantsDao() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.di 2 | 3 | import android.content.Context 4 | import com.ramadan.takeaway.data.db.RestaurantsDAO 5 | import com.ramadan.takeaway.data.repository.RestaurantsRepository 6 | import com.ramadan.takeaway.data.repository.RestaurantsRepositoryImpl 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | class RepositoryModule { 17 | @Singleton 18 | @Provides 19 | fun providesRestaurantsRepository(@ApplicationContext appContext: Context, dao: RestaurantsDAO): RestaurantsRepository { 20 | return RestaurantsRepositoryImpl(appContext, dao) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/domain/interactor/FavoriteRestaurantInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.domain.interactor 2 | 3 | import com.ramadan.takeaway.data.repository.RestaurantsRepository 4 | import com.ramadan.takeaway.domain.model.Restaurant 5 | import io.reactivex.Completable 6 | import io.reactivex.android.schedulers.AndroidSchedulers 7 | import io.reactivex.schedulers.Schedulers 8 | import javax.inject.Inject 9 | 10 | class FavoriteRestaurantInteractor @Inject constructor(private val repository: RestaurantsRepository) : 11 | Usecase { 12 | 13 | override fun execute(param: Restaurant): Completable { 14 | return repository.favoriteRestaurant(param) 15 | .subscribeOn(Schedulers.io()) 16 | .observeOn(AndroidSchedulers.mainThread()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/domain/interactor/GetRestaurantsInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.domain.interactor 2 | 3 | import com.ramadan.takeaway.data.repository.RestaurantsRepository 4 | import com.ramadan.takeaway.domain.model.Restaurant 5 | import io.reactivex.Single 6 | import io.reactivex.android.schedulers.AndroidSchedulers 7 | import io.reactivex.schedulers.Schedulers 8 | import javax.inject.Inject 9 | 10 | class GetRestaurantsInteractor @Inject constructor(private val repository: RestaurantsRepository) : Usecase>> { 11 | 12 | override fun execute(param: Unit): Single> { 13 | return repository.getRestaurants() 14 | .subscribeOn(Schedulers.io()) 15 | .observeOn(AndroidSchedulers.mainThread()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/domain/interactor/UnFavoriteRestaurantInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.domain.interactor 2 | 3 | import com.ramadan.takeaway.data.repository.RestaurantsRepository 4 | import com.ramadan.takeaway.domain.model.Restaurant 5 | import io.reactivex.Completable 6 | import io.reactivex.android.schedulers.AndroidSchedulers 7 | import io.reactivex.schedulers.Schedulers 8 | import javax.inject.Inject 9 | 10 | class UnFavoriteRestaurantInteractor @Inject constructor(private val repository: RestaurantsRepository) : 11 | Usecase { 12 | 13 | override fun execute(param: Restaurant): Completable { 14 | return repository.unFavoriteRestaurant(param) 15 | .subscribeOn(Schedulers.io()) 16 | .observeOn(AndroidSchedulers.mainThread()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/domain/interactor/Usecase.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.domain.interactor 2 | interface Usecase { 3 | fun execute(param: P): R 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/domain/model/Restaurant.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.domain.model 2 | 3 | data class Restaurant( 4 | val name: String = "", 5 | val status: String = "", 6 | val averageProductPrice: Double = 0.0, 7 | val bestMatch: Double = 0.0, 8 | val distance: Double = 0.0, 9 | val deliveryCosts: Double = 0.0, 10 | val popularity: Double = 0.0, 11 | val newest: Double = 0.0, 12 | val minCost: Double = 0.0, 13 | val ratingAverage: Double = 0.0, 14 | var isFavorite: Boolean = false 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/ui/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui 2 | 3 | import android.app.SearchManager 4 | import android.content.Context 5 | import android.os.Bundle 6 | import android.view.Menu 7 | import android.view.MenuInflater 8 | import android.view.MenuItem 9 | import android.view.View 10 | import android.widget.PopupMenu 11 | import android.widget.SearchView 12 | import androidx.fragment.app.Fragment 13 | import androidx.fragment.app.viewModels 14 | import androidx.lifecycle.Observer 15 | import androidx.navigation.findNavController 16 | import androidx.navigation.fragment.NavHostFragment 17 | import androidx.navigation.fragment.findNavController 18 | import androidx.recyclerview.widget.DividerItemDecoration 19 | import androidx.recyclerview.widget.LinearLayoutManager 20 | import androidx.recyclerview.widget.RecyclerView 21 | import com.ramadan.takeaway.R 22 | import com.ramadan.takeaway.ui.adapter.RestaurantsAdapter 23 | import com.ramadan.takeaway.ui.model.RestaurantModel 24 | import com.ramadan.takeaway.ui.sort.SortingOptionsHandler 25 | import com.ramadan.takeaway.ui.viewmodel.RestaurantsViewModel 26 | import com.ramadan.takeaway.util.DataState 27 | import com.ramadan.takeaway.util.SortingKeys 28 | import dagger.hilt.android.AndroidEntryPoint 29 | import kotlinx.android.synthetic.main.activity_main.* 30 | import kotlinx.android.synthetic.main.fragment_home.* 31 | 32 | @AndroidEntryPoint 33 | class HomeFragment: Fragment(R.layout.fragment_home) { 34 | lateinit var adapter: RestaurantsAdapter 35 | private val restaurantsViewModel: RestaurantsViewModel by viewModels() 36 | private var currentRestaurantModel: RestaurantModel? = null 37 | private var currentSortKey: SortingKeys = SortingKeys.BEST_MATCH 38 | private lateinit var searchView: SearchView 39 | 40 | private fun RecyclerView.setup(context: Context) { 41 | this.layoutManager = LinearLayoutManager(context) 42 | this.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) 43 | } 44 | 45 | val clickListener: (RestaurantModel) -> Unit = { data -> 46 | currentRestaurantModel = data 47 | if (!data.isFavorite) 48 | restaurantsViewModel.favoriteRestaurant(data) 49 | else 50 | restaurantsViewModel.unFavoriteRestaurant(data) 51 | } 52 | 53 | 54 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 55 | super.onViewCreated(view, savedInstanceState) 56 | setHasOptionsMenu(true) 57 | recyclerViewList.also { it.setup(requireContext()) } 58 | adapter = RestaurantsAdapter(clickListener = clickListener) 59 | recyclerViewList.adapter = adapter 60 | textViewSelectedSort.text = "SortBy: ${getSortingString(currentSortKey)}" 61 | handleSortBtn() 62 | restaurantsViewModel.getRestaurants() 63 | subscribeForObserver() 64 | } 65 | 66 | private fun handleSortBtn() { 67 | sortBtn.setOnClickListener { 68 | showPopup(it) 69 | } 70 | } 71 | 72 | private fun showPopup(v: View) { 73 | PopupMenu(requireContext(), v).apply { 74 | setOnMenuItemClickListener { item-> 75 | onMenuItemClick(item) 76 | true 77 | } 78 | inflate(R.menu.menu_sort_actions) 79 | show() 80 | } 81 | } 82 | 83 | private fun subscribeForObserver() { 84 | // observe data 85 | restaurantsViewModel.dataState.observe( 86 | viewLifecycleOwner, 87 | Observer { 88 | when (it) { 89 | is DataState.Success> -> { 90 | handleLoading(false) 91 | restaurantsViewModel.dataSet = it.data as ArrayList 92 | adapter.addRestaurants(it.data) 93 | } 94 | is DataState.Error -> { 95 | handleLoading(false) 96 | displayError(it.exception.message) 97 | } 98 | is DataState.Loading -> { 99 | handleLoading(true) 100 | } 101 | } 102 | } 103 | ) 104 | // observe sort key 105 | restaurantsViewModel.sortingOptions.observe( 106 | viewLifecycleOwner, 107 | Observer { 108 | textViewSelectedSort.text = "SortBy: ${getSortingString(it)}" 109 | textViewSelectedSort.setTextColor(resources.getColor(R.color.secondaryDarkColor)) 110 | adapter.changeSortType(SortingOptionsHandler.buildSortByOptionsComparator(it)) 111 | } 112 | ) 113 | 114 | // observe favorite Status 115 | restaurantsViewModel.favoriteState.observe( 116 | viewLifecycleOwner, 117 | Observer { 118 | if (it is DataState.Success) { 119 | currentRestaurantModel?.isFavorite = it.data.isFavorite 120 | restaurantsViewModel.setSelectedSortingOption(currentSortKey) 121 | } 122 | } 123 | ) 124 | } 125 | 126 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 127 | activity?.menuInflater?.inflate(R.menu.menu_search, menu) 128 | super.onCreateOptionsMenu(menu, inflater) 129 | // Associate searchable configuration with the SearchView 130 | val searchManager = activity?.getSystemService(Context.SEARCH_SERVICE) as SearchManager 131 | searchView = menu.findItem(R.id.action_search).actionView as SearchView 132 | searchView.setSearchableInfo( 133 | searchManager 134 | .getSearchableInfo(activity?.componentName) 135 | ) 136 | searchView.maxWidth = Integer.MAX_VALUE 137 | 138 | // listening to search query text change 139 | searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { 140 | override fun onQueryTextSubmit(query: String): Boolean { 141 | filterRestaurantsByKeyword(query) 142 | return false 143 | } 144 | 145 | override fun onQueryTextChange(query: String) = onQueryTextSubmit(query) 146 | }) 147 | } 148 | 149 | 150 | fun filterRestaurantsByKeyword(keyword: String) { 151 | val filteredList = restaurantsViewModel.filterRestaurantsByKeyword(keyword) 152 | adapter.addRestaurants(filteredList) 153 | } 154 | 155 | fun displayError(message: String?) { 156 | message?.let { textView_List_callToAction.text = it } 157 | } 158 | 159 | fun handleLoading(isDisplayed: Boolean) { 160 | progressBar.visibility = if (isDisplayed) View.VISIBLE else View.GONE 161 | textView_List_callToAction.visibility = if (isDisplayed) View.VISIBLE else View.GONE 162 | } 163 | 164 | private fun onMenuItemClick(item: MenuItem?): Boolean { 165 | val sortingKey = item?.itemId?.let { getSortingKey(it) } 166 | sortingKey?.let { 167 | currentSortKey = it 168 | restaurantsViewModel.setSelectedSortingOption(it) 169 | } 170 | return true 171 | } 172 | 173 | 174 | 175 | private fun getSortingKey(key: Int): SortingKeys { 176 | return when (key) { 177 | R.id.action_averageProductPrice -> SortingKeys.AVERAGE_PRODUCT_PRICE 178 | R.id.action_bestMatch -> SortingKeys.BEST_MATCH 179 | R.id.action_deliveryCosts -> SortingKeys.DELIVERY_COSTS 180 | R.id.action_distance -> SortingKeys.DISTANCE 181 | R.id.action_minCost -> SortingKeys.MIN_COST 182 | R.id.action_newest -> SortingKeys.NEWEST 183 | R.id.action_popularity -> SortingKeys.POPULARITY 184 | R.id.action_ratingAverage -> SortingKeys.RATING_AVERAGE 185 | else -> SortingKeys.BEST_MATCH 186 | } 187 | } 188 | 189 | private fun getSortingString(key: SortingKeys): String { 190 | return when (key) { 191 | SortingKeys.AVERAGE_PRODUCT_PRICE -> getString(R.string.action_averageProductPrice) 192 | SortingKeys.BEST_MATCH -> getString(R.string.action_bestMatch) 193 | SortingKeys.DISTANCE -> getString(R.string.action_distance) 194 | SortingKeys.DELIVERY_COSTS -> getString(R.string.action_deliveryCosts) 195 | SortingKeys.POPULARITY -> getString(R.string.action_popularity) 196 | SortingKeys.NEWEST -> getString(R.string.action_newest) 197 | SortingKeys.MIN_COST -> getString(R.string.action_minCost) 198 | SortingKeys.RATING_AVERAGE -> getString(R.string.action_ratingAverage) 199 | } 200 | } 201 | 202 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 203 | when(item.itemId){ 204 | R.id.action_settings -> { 205 | findNavController().navigate(R.id.actionFromHomeToSettings) 206 | } 207 | } 208 | return true 209 | } 210 | 211 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui 2 | 3 | import android.os.Bundle 4 | import androidx.activity.viewModels 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.ramadan.settings.SettingsViewModel 7 | import com.ramadan.takeaway.BuildConfig 8 | import com.ramadan.takeaway.R 9 | import dagger.hilt.android.AndroidEntryPoint 10 | 11 | 12 | @AndroidEntryPoint 13 | class MainActivity : AppCompatActivity() { 14 | 15 | private val settingsViewModel: SettingsViewModel by viewModels() 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | setContentView(R.layout.activity_main) 19 | settingsViewModel.setVersionName(BuildConfig.VERSION_NAME) 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/ui/adapter/RestaurantSortedListImpl.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui.adapter 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import com.ramadan.takeaway.ui.model.RestaurantModel 5 | import com.ramadan.takeaway.ui.sort.SortingOptionsHandler 6 | import com.ramadan.takeaway.ui.sort.wraper.SortedListComparatorWrapper 7 | import com.ramadan.takeaway.util.SortingKeys 8 | 9 | class RestaurantSortedListImpl(adapter: RecyclerView.Adapter<*>) : SortedListComparatorWrapper( 10 | adapter, 11 | DEFAULT_ORDER 12 | ) { 13 | 14 | companion object { 15 | private val DEFAULT_ORDER = SortingOptionsHandler.buildSortByOptionsComparator(SortingKeys.BEST_MATCH) 16 | } 17 | 18 | override fun areContentsTheSame(oldItem: RestaurantModel, newItem: RestaurantModel): Boolean { 19 | return oldItem == newItem 20 | } 21 | 22 | override fun areItemsTheSame(item1: RestaurantModel, item2: RestaurantModel): Boolean { 23 | return item1 == item2 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/ui/adapter/RestaurantsAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.RecyclerView 9 | import androidx.recyclerview.widget.SortedList 10 | import com.ramadan.takeaway.R 11 | import com.ramadan.takeaway.ui.model.RestaurantModel 12 | import com.ramadan.takeaway.ui.sort.wraper.SortedListComparatorWrapper 13 | import javax.inject.Inject 14 | 15 | class RestaurantsAdapter @Inject constructor(val clickListener: (RestaurantModel) -> Unit) : 16 | RecyclerView.Adapter() { 17 | private val restaurantSortedList: SortedList 18 | private val sortedListComparatorWrapper: SortedListComparatorWrapper 19 | 20 | init { 21 | sortedListComparatorWrapper = 22 | RestaurantSortedListImpl(this) 23 | restaurantSortedList = SortedList( 24 | RestaurantModel::class.java, 25 | SortedList.BatchedCallback(sortedListComparatorWrapper) 26 | ) 27 | } 28 | 29 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RestaurantsViewHolder { 30 | val view = LayoutInflater.from(parent.context) 31 | .inflate(R.layout.item_restaurant_view, parent, false) 32 | return RestaurantsViewHolder( 33 | view 34 | ) 35 | } 36 | 37 | override fun onBindViewHolder(holder: RestaurantsViewHolder, position: Int) { 38 | holder.bindRestaurant(restaurantSortedList[position], clickListener) 39 | } 40 | 41 | override fun getItemCount() = restaurantSortedList.size() 42 | 43 | fun addRestaurants(restaurants: List) { 44 | restaurantSortedList.clear() 45 | with(restaurantSortedList) { 46 | beginBatchedUpdates() 47 | addAll(restaurants) 48 | endBatchedUpdates() 49 | } 50 | } 51 | 52 | fun changeSortType(comparator: Comparator) { 53 | with(restaurantSortedList) { 54 | sortedListComparatorWrapper.setComparator(comparator) 55 | beginBatchedUpdates() 56 | val tempRestaurants = 57 | (0 until restaurantSortedList.size()).mapTo(ArrayList()) { get(it) } 58 | clear() 59 | addAll(tempRestaurants) 60 | tempRestaurants.clear() 61 | endBatchedUpdates() 62 | } 63 | } 64 | 65 | class RestaurantsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 66 | val name: TextView = itemView.findViewById(R.id.textView_restaurant_name) 67 | val status: TextView = itemView.findViewById(R.id.textView_restaurant_open_status) 68 | val favoriteImage: ImageView = itemView.findViewById(R.id.imageView_ListItem_favorite) 69 | fun bindRestaurant( 70 | restaurantModel: RestaurantModel, 71 | clickListener: (RestaurantModel) -> Unit 72 | ) { 73 | name.text = restaurantModel.name 74 | status.text = restaurantModel.status 75 | if (restaurantModel.isFavorite) 76 | favoriteImage.setImageResource(R.drawable.ic_favorite_remove) 77 | else 78 | favoriteImage.setImageResource(R.drawable.ic_favorite_add) 79 | 80 | favoriteImage.setOnClickListener { 81 | clickListener(restaurantModel) 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/ui/mapper/RestaurantModelMapper.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui.mapper 2 | 3 | import com.ramadan.takeaway.domain.model.Restaurant 4 | import com.ramadan.takeaway.ui.model.RestaurantModel 5 | import com.ramadan.takeaway.util.EntityMapper 6 | 7 | object RestaurantModelMapper : EntityMapper { 8 | 9 | override fun mapFromEntity(entity: Restaurant): RestaurantModel { 10 | return RestaurantModel( 11 | name = entity.name, 12 | status = entity.status, 13 | averageProductPrice = entity.averageProductPrice, 14 | bestMatch = entity.bestMatch, 15 | distance = entity.distance, 16 | deliveryCosts = entity.deliveryCosts, 17 | popularity = entity.popularity, 18 | newest = entity.newest, 19 | minCost = entity.minCost, 20 | ratingAverage = entity.ratingAverage, 21 | isFavorite = entity.isFavorite 22 | ) 23 | } 24 | 25 | override fun mapToEntity(uiModel: RestaurantModel): Restaurant { 26 | return Restaurant( 27 | name = uiModel.name, 28 | status = uiModel.status, 29 | averageProductPrice = uiModel.averageProductPrice, 30 | bestMatch = uiModel.bestMatch, 31 | distance = uiModel.distance, 32 | deliveryCosts = uiModel.deliveryCosts, 33 | popularity = uiModel.popularity, 34 | newest = uiModel.newest, 35 | minCost = uiModel.minCost, 36 | ratingAverage = uiModel.ratingAverage, 37 | isFavorite = uiModel.isFavorite 38 | ) 39 | } 40 | 41 | fun mapFromEntityList(entities: List): List { 42 | return entities.map { mapFromEntity(it) } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/ui/model/RestaurantModel.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui.model 2 | 3 | data class RestaurantModel( 4 | val name: String, 5 | val status: String, 6 | val averageProductPrice: Double, 7 | val bestMatch: Double, 8 | val distance: Double, 9 | val deliveryCosts: Double, 10 | val popularity: Double, 11 | val newest: Double, 12 | val minCost: Double, 13 | val ratingAverage: Double, 14 | var isFavorite: Boolean = false 15 | ) 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/ui/sort/SortingOptionsHandler.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui.sort 2 | 3 | import com.ramadan.takeaway.ui.model.RestaurantModel 4 | import com.ramadan.takeaway.util.OpeningState 5 | import com.ramadan.takeaway.util.SortingKeys 6 | import java.util.Locale 7 | import kotlin.Comparator 8 | 9 | object SortingOptionsHandler { 10 | fun buildSortByOptionsComparator(sortingKey: SortingKeys?): Comparator { 11 | val byFavorites = compareByDescending { restaurant -> restaurant.isFavorite } 12 | val byOpeningState: (RestaurantModel) -> OpeningState = { restaurant -> 13 | val status = 14 | restaurant.status.toUpperCase(Locale.getDefault()).replace(" ", "_") 15 | OpeningState.valueOf(status) 16 | } 17 | 18 | return when (sortingKey) { 19 | SortingKeys.AVERAGE_PRODUCT_PRICE -> 20 | byFavorites 21 | .thenBy(byOpeningState) 22 | .thenByDescending { restaurant -> restaurant.averageProductPrice } 23 | 24 | SortingKeys.DISTANCE -> 25 | byFavorites 26 | .thenBy(byOpeningState) 27 | .thenByDescending { restaurant -> restaurant.distance } 28 | SortingKeys.DELIVERY_COSTS -> 29 | byFavorites 30 | .thenBy(byOpeningState) 31 | .thenByDescending { restaurant -> restaurant.deliveryCosts } 32 | SortingKeys.POPULARITY -> 33 | byFavorites 34 | .thenBy(byOpeningState) 35 | .thenByDescending { restaurant -> restaurant.popularity } 36 | SortingKeys.NEWEST -> 37 | byFavorites 38 | .thenBy(byOpeningState) 39 | .thenByDescending { restaurant -> restaurant.newest } 40 | SortingKeys.MIN_COST -> 41 | byFavorites 42 | .thenBy(byOpeningState) 43 | .thenByDescending { restaurant -> restaurant.minCost } 44 | SortingKeys.RATING_AVERAGE -> 45 | byFavorites 46 | .thenBy(byOpeningState) 47 | .thenByDescending { restaurant -> restaurant.ratingAverage } 48 | // Default. 49 | else -> 50 | byFavorites 51 | .thenBy(byOpeningState) 52 | .thenByDescending { restaurant -> restaurant.bestMatch } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/ui/sort/wraper/SortedListComparatorWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui.sort.wraper 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import androidx.recyclerview.widget.SortedListAdapterCallback 5 | abstract class SortedListComparatorWrapper(adapter: RecyclerView.Adapter<*>, private var comparator: Comparator?) : SortedListAdapterCallback(adapter) { 6 | 7 | fun setComparator(comparator: Comparator) { 8 | if (comparator == this.comparator) { 9 | return 10 | } 11 | 12 | this.comparator = comparator 13 | } 14 | 15 | override fun compare(o1: T, o2: T) = comparator!!.compare(o1, o2) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/ui/viewmodel/RestaurantsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui.viewmodel 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import com.ramadan.takeaway.domain.interactor.FavoriteRestaurantInteractor 8 | import com.ramadan.takeaway.domain.interactor.GetRestaurantsInteractor 9 | import com.ramadan.takeaway.domain.interactor.UnFavoriteRestaurantInteractor 10 | import com.ramadan.takeaway.domain.model.Restaurant 11 | import com.ramadan.takeaway.ui.mapper.RestaurantModelMapper 12 | import com.ramadan.takeaway.ui.model.RestaurantModel 13 | import com.ramadan.takeaway.util.DataState 14 | import com.ramadan.takeaway.util.SortingKeys 15 | import dagger.hilt.android.lifecycle.HiltViewModel 16 | import io.reactivex.disposables.CompositeDisposable 17 | import java.util.Locale 18 | import javax.inject.Inject 19 | import kotlin.collections.ArrayList 20 | 21 | @HiltViewModel 22 | class RestaurantsViewModel @Inject constructor( 23 | private val getRestaurantsInteractor: GetRestaurantsInteractor, 24 | private val favoriteRestaurantInteractor: FavoriteRestaurantInteractor, 25 | private val unFavoriteRestaurantInteractor: UnFavoriteRestaurantInteractor 26 | ) : ViewModel() { 27 | private val compositeDisposable = CompositeDisposable() 28 | private val _dataState: MutableLiveData>> = MutableLiveData() 29 | val dataState: LiveData>> 30 | get() = _dataState 31 | private val _sortingOptions: MutableLiveData = MutableLiveData() 32 | val sortingOptions: LiveData 33 | get() = _sortingOptions 34 | private val _favoriteState: MutableLiveData> = MutableLiveData() 35 | val favoriteState: LiveData> 36 | get() = _favoriteState 37 | 38 | var dataSet = ArrayList() 39 | 40 | fun getRestaurants() { 41 | if (_dataState.value != null) return 42 | 43 | _dataState.value = DataState.Loading 44 | compositeDisposable.add( 45 | getRestaurantsInteractor.execute(Unit).subscribe( 46 | { res -> 47 | _dataState.value = 48 | DataState.Success(RestaurantModelMapper.mapFromEntityList(res)) 49 | }, 50 | { error -> _dataState.value = DataState.Error(error as RuntimeException) } 51 | ) 52 | ) 53 | } 54 | 55 | fun favoriteRestaurant(restaurantModel: RestaurantModel) { 56 | restaurantModel.isFavorite = true 57 | compositeDisposable.add( 58 | favoriteRestaurantInteractor.execute(RestaurantModelMapper.mapToEntity(restaurantModel)) 59 | .subscribe { 60 | _favoriteState.value = DataState.Success(restaurantModel) 61 | } 62 | ) 63 | } 64 | 65 | fun unFavoriteRestaurant(restaurantModel: RestaurantModel) { 66 | restaurantModel.isFavorite = false 67 | compositeDisposable.add( 68 | unFavoriteRestaurantInteractor.execute(RestaurantModelMapper.mapToEntity(restaurantModel)) 69 | .subscribe { 70 | _favoriteState.value = DataState.Success(restaurantModel) 71 | } 72 | ) 73 | } 74 | fun filterRestaurantsByKeyword(keyword: String): 75 | List { 76 | val filteredList = ArrayList() 77 | dataSet.forEach { 78 | if (it.name.toLowerCase(Locale.getDefault()).contains(keyword.toLowerCase(Locale.getDefault()))) 79 | filteredList.add(it) 80 | } 81 | return filteredList 82 | } 83 | 84 | override fun onCleared() { 85 | super.onCleared() 86 | compositeDisposable.clear() 87 | } 88 | 89 | fun setSelectedSortingOption(key: SortingKeys) { 90 | _sortingOptions.value = key 91 | } 92 | 93 | @VisibleForTesting 94 | fun changeDataStateForRestaurants(data: List) { 95 | _dataState.value = DataState.Success(RestaurantModelMapper.mapFromEntityList(data)) 96 | } 97 | 98 | @VisibleForTesting 99 | fun changeDataStateForRestaurant(data: Restaurant) { 100 | _favoriteState.value = DataState.Success(RestaurantModelMapper.mapFromEntity(data)) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/util/DataState.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.util 2 | 3 | sealed class DataState { 4 | data class Success(val data: T) : DataState() 5 | data class Error(val exception: Exception) : DataState() 6 | object Loading : DataState() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/util/EntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.util 2 | 3 | interface EntityMapper { 4 | fun mapFromEntity(entity: Entity): DomainModel 5 | fun mapToEntity(domainModel: DomainModel): Entity 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/util/OpeningState.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.util 2 | 3 | @Suppress("UNUSED_PARAMETER") 4 | enum class OpeningState(state: Int) { 5 | OPEN(1), ORDER_AHEAD(2), CLOSED(3) 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/util/RestaurantDiffUtils.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.util 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import com.ramadan.takeaway.ui.model.RestaurantModel 5 | import javax.inject.Inject 6 | 7 | class RestaurantDiffUtils @Inject constructor( 8 | private var oldRestaurants: MutableList, 9 | private var newRestaurants: MutableList 10 | ) : DiffUtil.Callback() { 11 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 12 | return oldRestaurants[oldItemPosition].name == newRestaurants[newItemPosition].name && 13 | oldRestaurants[oldItemPosition].isFavorite == newRestaurants[newItemPosition].isFavorite 14 | } 15 | 16 | override fun getOldListSize(): Int = oldRestaurants.size 17 | 18 | override fun getNewListSize(): Int = newRestaurants.size 19 | 20 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 21 | return oldRestaurants[oldItemPosition] == newRestaurants[newItemPosition] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/ramadan/takeaway/util/SortingKeys.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.util 2 | 3 | enum class SortingKeys { 4 | AVERAGE_PRODUCT_PRICE, 5 | BEST_MATCH, 6 | DISTANCE, 7 | DELIVERY_COSTS, 8 | POPULARITY, 9 | NEWEST, 10 | MIN_COST, 11 | RATING_AVERAGE, 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_debug_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 10 | 16 | 19 | 22 | 23 | 24 | 25 | 31 | -------------------------------------------------------------------------------- /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/ic_favorite_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_remove.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorites.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_filter.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sort.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_text_found.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_favorite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 52 | 53 | 54 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_restaurant_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 22 | 23 | 32 | 33 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_sort_actions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 28 | 29 | 33 | 34 | 38 | 39 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_debug.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_debug_round.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/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-hdpi/ic_launcher_debug.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_debug_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-hdpi/ic_launcher_debug_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-mdpi/ic_launcher_debug.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_debug_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-mdpi/ic_launcher_debug_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xhdpi/ic_launcher_debug.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_debug_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xhdpi/ic_launcher_debug_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xxhdpi/ic_launcher_debug.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_debug_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xxhdpi/ic_launcher_debug_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xxxhdpi/ic_launcher_debug.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_debug_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xxxhdpi/ic_launcher_debug_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/navigation/nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffffff 4 | #b47cff 5 | #000000 6 | #d500f9 7 | #ff5bff 8 | #9e00c5 9 | #ffffff 10 | #b47cff 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #7c4dff 4 | #b47cff 5 | #3f1dcb 6 | #d500f9 7 | #ff5bff 8 | #9e00c5 9 | #ffffff 10 | #000000 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2dp 4 | 4dp 5 | 8dp 6 | 12dp 7 | 16dp 8 | 24dp 9 | 32dp 10 | 36dp 11 | 40dp 12 | 48dp 13 | 30dp 14 | 64dp 15 | 72dp 16 | 80dp 17 | 160dp 18 | 19 | 20 | 12sp 21 | 14sp 22 | 16sp 23 | 18sp 24 | 22sp 25 | 24sp 26 | 34sp 27 | 45sp 28 | 56sp 29 | 112sp 30 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_debug_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #DCA73D 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Restaurants 3 | Restaurants Debug 4 | 5 | 6 | Favorites 7 | 8 | 9 | Favorites 10 | There are no favorite! 11 | 12 | 13 | Field is required! 14 | Please enter a valid input! 15 | Sorry, genre not found! 16 | No Network! 17 | Sorry, something goes wrong while trying to connect! 18 | No restaurants available! 19 | Bookmark failed! 20 | Search 21 | Sort 22 | Sort 23 | Restaurant Name 24 | Best match 25 | Newest 26 | Rating average 27 | Distance 28 | Popularity 29 | Average Product Price 30 | Delivery Costs 31 | Min Cost 32 | Sort By 33 | 34 | 35 | 36 | Choose Theme 37 | Settings 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/values/theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/xml/searchable.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/test/java/com/ramadan/takeaway/ui/sort/SortingOptionsHandlerTest.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui.sort 2 | 3 | import com.ramadan.takeaway.ui.model.RestaurantModel 4 | import com.ramadan.takeaway.util.SortingKeys 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | 8 | class SortingOptionsHandlerTest { 9 | companion object { 10 | const val SUSHI_RESTAURANT = "Tanoshii Sushi" 11 | const val EXPRESS_RESTAURANT = "Tandoori Express" 12 | const val ROYAL_RESTAURANT = "Royal Thai" 13 | } 14 | @Test 15 | fun `test build sort by options comparator sort data by best match`() { 16 | val rest1 = RestaurantModel( 17 | name = SUSHI_RESTAURANT, 18 | status = "open", 19 | bestMatch = 3.0, 20 | newest = 96.0, 21 | ratingAverage = 4.5, 22 | distance = 1190.0, 23 | popularity = 17.0, 24 | averageProductPrice = 1536.0, 25 | deliveryCosts = 200.0, 26 | minCost = 1000.0 27 | ) 28 | 29 | val rest2 = RestaurantModel( 30 | name = EXPRESS_RESTAURANT, 31 | status = "open", 32 | bestMatch = 40.0, 33 | newest = 96.0, 34 | ratingAverage = 4.5, 35 | distance = 1190.0, 36 | popularity = 17.0, 37 | averageProductPrice = 1536.0, 38 | deliveryCosts = 200.0, 39 | minCost = 1000.0 40 | ) 41 | 42 | val rest3 = RestaurantModel( 43 | name = ROYAL_RESTAURANT, 44 | status = "open", 45 | bestMatch = 10.0, 46 | newest = 96.0, 47 | ratingAverage = 4.5, 48 | distance = 1190.0, 49 | popularity = 17.0, 50 | averageProductPrice = 1536.0, 51 | deliveryCosts = 200.0, 52 | minCost = 1000.0 53 | ) 54 | val list = mutableListOf(rest1, rest2, rest3) 55 | val bestMatchComparator = 56 | SortingOptionsHandler.buildSortByOptionsComparator(SortingKeys.BEST_MATCH) 57 | list.sortWith(bestMatchComparator) 58 | assertEquals(list.indexOf(rest2), 0) 59 | assertEquals(list.indexOf(rest3), 1) 60 | assertEquals(list.indexOf(rest1), 2) 61 | } 62 | 63 | @Test 64 | fun `test build sort by options comparator sort data by open State`() { 65 | 66 | val rest1 = RestaurantModel( 67 | name = SUSHI_RESTAURANT, 68 | status = "closed", 69 | bestMatch = 3.0, 70 | newest = 96.0, 71 | ratingAverage = 4.5, 72 | distance = 1190.0, 73 | popularity = 17.0, 74 | averageProductPrice = 1536.0, 75 | deliveryCosts = 200.0, 76 | minCost = 1000.0 77 | ) 78 | 79 | val rest2 = RestaurantModel( 80 | name = EXPRESS_RESTAURANT, 81 | status = "order ahead", 82 | bestMatch = 40.0, 83 | newest = 96.0, 84 | ratingAverage = 4.5, 85 | distance = 1190.0, 86 | popularity = 17.0, 87 | averageProductPrice = 1536.0, 88 | deliveryCosts = 200.0, 89 | minCost = 1000.0 90 | ) 91 | 92 | val rest3 = RestaurantModel( 93 | name = ROYAL_RESTAURANT, 94 | status = "open", 95 | bestMatch = 10.0, 96 | newest = 96.0, 97 | ratingAverage = 4.5, 98 | distance = 1190.0, 99 | popularity = 17.0, 100 | averageProductPrice = 1536.0, 101 | deliveryCosts = 200.0, 102 | minCost = 1000.0 103 | ) 104 | val list = mutableListOf(rest1, rest2, rest3) 105 | 106 | val bestMatchComparator = 107 | SortingOptionsHandler.buildSortByOptionsComparator(SortingKeys.BEST_MATCH) 108 | 109 | list.sortWith(bestMatchComparator) 110 | 111 | assertEquals(list.indexOf(rest3), 0) 112 | assertEquals(list.indexOf(rest2), 1) 113 | assertEquals(list.indexOf(rest1), 2) 114 | } 115 | 116 | @Test 117 | fun `test build sort by options comparator sort data by favorite`() { 118 | val rest1 = RestaurantModel( 119 | name = SUSHI_RESTAURANT, 120 | status = "closed", 121 | bestMatch = 3.0, 122 | newest = 96.0, 123 | ratingAverage = 4.5, 124 | distance = 1190.0, 125 | popularity = 17.0, 126 | averageProductPrice = 1536.0, 127 | deliveryCosts = 200.0, 128 | minCost = 1000.0 129 | ) 130 | 131 | val rest2 = RestaurantModel( 132 | name = EXPRESS_RESTAURANT, 133 | status = "order ahead", 134 | bestMatch = 40.0, 135 | newest = 96.0, 136 | ratingAverage = 4.5, 137 | distance = 1190.0, 138 | popularity = 17.0, 139 | averageProductPrice = 1536.0, 140 | deliveryCosts = 200.0, 141 | minCost = 1000.0, 142 | isFavorite = true 143 | ) 144 | 145 | val rest3 = RestaurantModel( 146 | name = ROYAL_RESTAURANT, 147 | status = "open", 148 | bestMatch = 10.0, 149 | newest = 96.0, 150 | ratingAverage = 4.5, 151 | distance = 1190.0, 152 | popularity = 17.0, 153 | averageProductPrice = 1536.0, 154 | deliveryCosts = 200.0, 155 | minCost = 1000.0 156 | ) 157 | 158 | val list = mutableListOf(rest1, rest2, rest3) 159 | 160 | val bestMatchComparator = SortingOptionsHandler.buildSortByOptionsComparator(SortingKeys.BEST_MATCH) 161 | 162 | list.sortWith(bestMatchComparator) 163 | 164 | assertEquals(list.indexOf(rest2), 0) 165 | assertEquals(list.indexOf(rest3), 1) 166 | assertEquals(list.indexOf(rest1), 2) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /app/src/test/java/com/ramadan/takeaway/ui/viewmodel/RestaurantsViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.takeaway.ui.viewmodel 2 | 3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule 4 | import androidx.lifecycle.Observer 5 | import com.ramadan.takeaway.domain.interactor.FavoriteRestaurantInteractor 6 | import com.ramadan.takeaway.domain.interactor.GetRestaurantsInteractor 7 | import com.ramadan.takeaway.domain.interactor.UnFavoriteRestaurantInteractor 8 | import com.ramadan.takeaway.domain.model.Restaurant 9 | import com.ramadan.takeaway.ui.mapper.RestaurantModelMapper 10 | import com.ramadan.takeaway.ui.model.RestaurantModel 11 | import com.ramadan.takeaway.util.DataState 12 | import com.ramadan.test_utils.RxSchedulerRule 13 | import com.ramadan.test_utils.mock 14 | import io.reactivex.Completable 15 | import io.reactivex.Single 16 | import org.junit.Assert.assertNotNull 17 | import org.junit.Before 18 | import org.junit.Rule 19 | import org.junit.Test 20 | import org.mockito.Mock 21 | import org.mockito.Mockito.`when` 22 | import org.mockito.Mockito.verify 23 | import org.mockito.MockitoAnnotations 24 | import java.lang.RuntimeException 25 | 26 | class RestaurantsViewModelTest { 27 | @get:Rule 28 | val instantTaskExecutorRule = InstantTaskExecutorRule() 29 | 30 | private lateinit var restaurantsViewModel: RestaurantsViewModel 31 | 32 | @Mock 33 | private lateinit var getRestaurantsInteractor: GetRestaurantsInteractor 34 | 35 | @Mock 36 | private lateinit var favoriteRestaurantInteractor: FavoriteRestaurantInteractor 37 | 38 | @Mock 39 | private lateinit var unFavoriteRestaurantInteractor: UnFavoriteRestaurantInteractor 40 | 41 | @Rule 42 | @JvmField 43 | var testSchedulerRule: RxSchedulerRule = RxSchedulerRule() 44 | 45 | @Mock 46 | private var restaurantsStateObserver: Observer>> = mock() 47 | 48 | @Mock 49 | private var favoriteStateObserver: Observer> = mock() 50 | 51 | @Before 52 | fun setup() { 53 | MockitoAnnotations.initMocks(this) 54 | restaurantsViewModel = RestaurantsViewModel( 55 | getRestaurantsInteractor, 56 | favoriteRestaurantInteractor, 57 | unFavoriteRestaurantInteractor 58 | ) 59 | .apply { 60 | dataState.observeForever(restaurantsStateObserver) 61 | favoriteState.observeForever(favoriteStateObserver) 62 | } 63 | } 64 | 65 | @Test 66 | fun `DemoViewModel is ready for test`() { 67 | assertNotNull(restaurantsViewModel) 68 | } 69 | 70 | @Test 71 | fun `should success when getRestaurants returns proper data`() { 72 | // Given 73 | val data = givenData() 74 | `when`(getRestaurantsInteractor.execute(Unit)) 75 | .thenReturn(Single.just(data)) 76 | // When 77 | getRestaurantsInteractor.execute(Unit) 78 | restaurantsViewModel.changeDataStateForRestaurants(data) 79 | // Then 80 | verify(restaurantsStateObserver).onChanged(DataState.Success(RestaurantModelMapper.mapFromEntityList(data))) 81 | } 82 | 83 | @Test(expected = RuntimeException::class) 84 | fun `should receive error state when getRestaurants throws Exception `() { 85 | // given 86 | `when`(getRestaurantsInteractor.execute(Unit)) 87 | .thenThrow(RuntimeException()) 88 | // when 89 | getRestaurantsInteractor.execute(Unit) 90 | // Then 91 | verify(restaurantsStateObserver).onChanged(DataState.Loading) 92 | verify(restaurantsStateObserver).onChanged(DataState.Error(RuntimeException())) 93 | } 94 | 95 | @Test 96 | fun `should success when favorite restaurant is success`() { 97 | // Given 98 | val data = givenRestaurant() 99 | `when`(favoriteRestaurantInteractor.execute(data)) 100 | .thenReturn(Completable.complete()) 101 | // When 102 | favoriteRestaurantInteractor.execute(data) 103 | restaurantsViewModel.changeDataStateForRestaurant(data) 104 | // Then 105 | verify(favoriteStateObserver).onChanged(DataState.Success(RestaurantModelMapper.mapFromEntity(data))) 106 | } 107 | 108 | @Test 109 | fun `should success when un favorite restaurant is success`() { 110 | // Given 111 | val data = givenRestaurant() 112 | `when`(unFavoriteRestaurantInteractor.execute(data)) 113 | .thenReturn(Completable.complete()) 114 | // When 115 | unFavoriteRestaurantInteractor.execute(data) 116 | restaurantsViewModel.changeDataStateForRestaurant(data) 117 | // Then 118 | verify(favoriteStateObserver).onChanged(DataState.Success(RestaurantModelMapper.mapFromEntity(data))) 119 | } 120 | 121 | private fun givenData(): List { 122 | val rest1 = Restaurant( 123 | name = "Tanoshii Sushi", 124 | status = "open", 125 | bestMatch = 0.0, 126 | newest = 96.0, 127 | ratingAverage = 4.5, 128 | distance = 1190.0, 129 | popularity = 17.0, 130 | averageProductPrice = 1536.0, 131 | deliveryCosts = 200.0, 132 | minCost = 1000.0 133 | ) 134 | 135 | val rest2 = Restaurant( 136 | name = "Tandoori Express", 137 | status = "open", 138 | bestMatch = 0.0, 139 | newest = 96.0, 140 | ratingAverage = 4.5, 141 | distance = 1190.0, 142 | popularity = 17.0, 143 | averageProductPrice = 1536.0, 144 | deliveryCosts = 200.0, 145 | minCost = 1000.0 146 | ) 147 | 148 | val rest3 = Restaurant( 149 | name = "Royal Thai", 150 | status = "closed", 151 | bestMatch = 0.0, 152 | newest = 96.0, 153 | ratingAverage = 4.5, 154 | distance = 1190.0, 155 | popularity = 17.0, 156 | averageProductPrice = 1536.0, 157 | deliveryCosts = 200.0, 158 | minCost = 1000.0 159 | ) 160 | val list = ArrayList() 161 | list.add(rest1) 162 | list.add(rest2) 163 | list.add(rest3) 164 | return list 165 | } 166 | 167 | private fun givenRestaurant() = Restaurant( 168 | name = "Tanoshii Sushi", 169 | status = "open", 170 | bestMatch = 0.0, 171 | newest = 96.0, 172 | ratingAverage = 4.5, 173 | distance = 1190.0, 174 | popularity = 17.0, 175 | averageProductPrice = 1536.0, 176 | deliveryCosts = 200.0, 177 | minCost = 1000.0 178 | ) 179 | } 180 | -------------------------------------------------------------------------------- /app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = Versions.KOTLIN 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath BuildPlugins.androidGradle 11 | classpath BuildPlugins.kotlinGradlePlugin 12 | classpath BuildPlugins.jacocoPlugin 13 | classpath BuildPlugins.hiltPlugin 14 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30' 15 | } 16 | } 17 | plugins { 18 | id "org.jlleitschuh.gradle.ktlint" version "9.3.0" 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | google() 24 | jcenter() 25 | } 26 | apply plugin: "org.jlleitschuh.gradle.ktlint" 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | 33 | apply plugin: "com.vanniktech.android.junit.jacoco" -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins{ 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories{ 6 | jcenter() 7 | } -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Android.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/classes/kotlin/main/Android.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/BuildPlugins.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/classes/kotlin/main/BuildPlugins.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Libs.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/classes/kotlin/main/Libs.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/META-INF/buildSrc.kotlin_module: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/TestLibs.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/classes/kotlin/main/TestLibs.class -------------------------------------------------------------------------------- /buildSrc/build/classes/kotlin/main/Versions.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/classes/kotlin/main/Versions.class -------------------------------------------------------------------------------- /buildSrc/build/kotlin/buildSrcjar-classes.txt: -------------------------------------------------------------------------------- 1 | /Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/build/classes/kotlin/main/Android.class:/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/build/classes/kotlin/main/BuildPlugins.class:/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/build/classes/kotlin/main/Libs.class:/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/build/classes/kotlin/main/TestLibs.class:/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/build/classes/kotlin/main/Versions.class -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/build-history.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/build-history.bin -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.keystream: -------------------------------------------------------------------------------- 1 | a/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.keystream.len: -------------------------------------------------------------------------------- 1 | b -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.values.at: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab.values.at -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/inputs/source-to-output.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream: -------------------------------------------------------------------------------- 1 | Versions BuildPluginsAndroidLibsTestLibs -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len: -------------------------------------------------------------------------------- 1 | , -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStorageba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.ktba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.ktba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.ktba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.ktba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream: -------------------------------------------------------------------------------- 1 | Versions BuildPluginsAndroidLibsTestLibs -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len: -------------------------------------------------------------------------------- 1 | , -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStorageba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.ktba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.ktba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.ktba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.ktba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream: -------------------------------------------------------------------------------- 1 | Versions BuildPluginsAndroidLibsTestLibs.kotlin_module -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len: -------------------------------------------------------------------------------- 1 | ; -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream: -------------------------------------------------------------------------------- 1 | a/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len: -------------------------------------------------------------------------------- 1 | b -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStorage;Versions BuildPluginsAndroidLibsTestLibs.kotlin_module -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/counters.tab: -------------------------------------------------------------------------------- 1 | 1 2 | 0 -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream: -------------------------------------------------------------------------------- 1 | a/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len: -------------------------------------------------------------------------------- 1 | b -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStorage -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/file-to-id.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/id-to-file.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStorageba/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/src/main/java/Dependencies.kt -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.keystream.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.len: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab.values.at: -------------------------------------------------------------------------------- 1 | /Header Record For PersistentHashMapValueStorage -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/caches-jvm/lookups/lookups.tab_i.len -------------------------------------------------------------------------------- /buildSrc/build/kotlin/compileKotlin/last-build.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/kotlin/compileKotlin/last-build.bin -------------------------------------------------------------------------------- /buildSrc/build/libs/buildSrc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/libs/buildSrc.jar -------------------------------------------------------------------------------- /buildSrc/build/pluginUnderTestMetadata/plugin-under-test-metadata.properties: -------------------------------------------------------------------------------- 1 | implementation-classpath=/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/build/classes/java/main\:/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/build/classes/groovy/main\:/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/build/classes/kotlin/main\:/Users/ramadan/Desktop/code/android-studio/AndroidTemplate/buildSrc/build/resources/main 2 | -------------------------------------------------------------------------------- /buildSrc/build/reports/plugin-development/validation-report.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/buildSrc/build/reports/plugin-development/validation-report.txt -------------------------------------------------------------------------------- /buildSrc/build/source-roots/buildSrc/source-roots.txt: -------------------------------------------------------------------------------- 1 | src/main/resources 2 | src/main/java 3 | src/main/groovy 4 | src/main/kotlin 5 | src/test/resources 6 | src/test/java 7 | src/test/groovy 8 | src/test/kotlin 9 | -------------------------------------------------------------------------------- /buildSrc/build/tmp/jar/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | 3 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/Dependencies.kt: -------------------------------------------------------------------------------- 1 | 2 | object Versions { 3 | val KOTLIN = "1.3.72" 4 | val COMPILE_SDK = 28 5 | val MIN_SDK_VERSION = 16 6 | val TARGET_SDK_VERSION = 28 7 | val VERSION_CODE = 1 8 | val VERSION_NAME = "1.0.0" 9 | val RXJAVA_VERSION = "2.2.13" 10 | val RX_ANDROID = "2.1.1" 11 | val GSON_VERSION = "2.8.8" 12 | val LIFE_CYCLE_VERSION = "2.2.0" 13 | val CARD_VIEW_VERSION = "1.0.0" 14 | val RECYCLERVIEW_VERSION = "1.1.0" 15 | val CONSTRAINT_LAYOUT = "1.1.3" 16 | val ANDROID_GRADLE_VERSION = "4.0.1" 17 | val APP_COMPAT_VERSION = "1.1.0" 18 | val CORE_KTX = "1.1.0" 19 | val ARCH_CORE_TESTING_VER = "2.0.0" 20 | val TEST_RUNNER_VER = "1.1.1" 21 | val RULES_VER = "1.1.1" 22 | val TRUTH_VER = "1.1.0" 23 | val JUNIT_EXT_VER = "1.1.0" 24 | val MATERIAL_VERSION = "1.4.0" 25 | val MOCKITO = "3.3.1" 26 | val MULTIDEX = "1.0.3" 27 | val JACOCO = "0.16.0" 28 | val HILT = "2.38.1" 29 | val FRAGMENT_KTX = "1.2.5" 30 | val ROOM = "2.2.5" 31 | val NAV_Version = "2.3.5" 32 | } 33 | 34 | object BuildPlugins { 35 | val androidGradle = "com.android.tools.build:gradle:${Versions.ANDROID_GRADLE_VERSION}" 36 | val kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.KOTLIN}" 37 | val jacocoPlugin = "com.vanniktech:gradle-android-junit-jacoco-plugin:${Versions.JACOCO}" 38 | val hiltPlugin = "com.google.dagger:hilt-android-gradle-plugin:${Versions.HILT}" 39 | } 40 | 41 | object Android { 42 | val minSDK = Versions.MIN_SDK_VERSION 43 | val targetSDK = Versions.TARGET_SDK_VERSION 44 | val versionCode = Versions.VERSION_CODE 45 | val versionName = Versions.VERSION_NAME 46 | val compileSDK = Versions.COMPILE_SDK 47 | val applicationId = "com.ramadan.takeaway" 48 | } 49 | 50 | object Libs { 51 | val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.KOTLIN}" 52 | val rxVersion = "io.reactivex.rxjava2:rxjava:${Versions.RXJAVA_VERSION}" 53 | val rxAndroid = "io.reactivex.rxjava2:rxandroid:${Versions.RX_ANDROID}" 54 | val material = "com.google.android.material:material:${Versions.MATERIAL_VERSION}" 55 | val gson = "com.google.code.gson:gson:${Versions.GSON_VERSION}" 56 | val recyclerview = "androidx.recyclerview:recyclerview:${Versions.RECYCLERVIEW_VERSION}" 57 | val cardview = "androidx.cardview:cardview:${Versions.CARD_VIEW_VERSION}" 58 | val constraintLayout = "androidx.constraintlayout:constraintlayout:${Versions.CONSTRAINT_LAYOUT}" 59 | val liveData = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.LIFE_CYCLE_VERSION}" 60 | val viewModel = "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.LIFE_CYCLE_VERSION}" 61 | val appCompat = "androidx.appcompat:appcompat:${Versions.APP_COMPAT_VERSION}" 62 | val coreExt = "androidx.core:core-ktx:${Versions.CORE_KTX}" 63 | val multidex = "com.android.support:multidex:${Versions.MULTIDEX}" 64 | val hiltAndroid = "com.google.dagger:hilt-android:${Versions.HILT}" 65 | val hiltAndroidCompiler = "com.google.dagger:hilt-android-compiler:${Versions.HILT}" 66 | val fragmentKtx = "androidx.fragment:fragment-ktx:${Versions.FRAGMENT_KTX}" 67 | val room = "androidx.room:room-runtime:${Versions.ROOM}" 68 | val roomCompiler = "androidx.room:room-compiler:${Versions.ROOM}" 69 | val rxRoom = "androidx.room:room-rxjava2:${Versions.ROOM}" 70 | // Kotlin 71 | val navigationKtx = "androidx.navigation:navigation-fragment-ktx:${Versions.NAV_Version}" 72 | val navigationUI = "androidx.navigation:navigation-ui-ktx:${Versions.NAV_Version}" 73 | } 74 | 75 | object TestLibs { 76 | val junit = "junit:junit:4.12" 77 | val archCoreTesting = "androidx.arch.core:core-testing:${Versions.ARCH_CORE_TESTING_VER}" 78 | val testRunner = "com.android.support.test:runner:${Versions.TEST_RUNNER_VER}" 79 | val rules = "androidx.test:rules:${Versions.RULES_VER}" 80 | val truth = "androidx.test.ext:truth:${Versions.TRUTH_VER}" 81 | val junitExt = "androidx.test.ext:junit:${Versions.JUNIT_EXT_VER}" 82 | val mockito = "org.mockito:mockito-core:${Versions.MOCKITO}" 83 | val mockitoAndroid = "org.mockito:mockito-android:${Versions.MOCKITO}" 84 | } -------------------------------------------------------------------------------- /easy_adb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | alias adb='/Users/ramadan/Library/Android/sdk/platform-tools/adb' 3 | PACKAGE="com.ramadan.takeaway" 4 | 5 | print_blue(){ 6 | printf "\e[1;34m$1\e[0m" 7 | } 8 | print_red(){ 9 | printf "\e[1;31m$1\e[0m" 10 | } 11 | print_green(){ 12 | printf "\e[1;32m$1\e[0m" 13 | } 14 | print_yellow(){ 15 | printf "\e[1;33m$1\e[0m" 16 | } 17 | 18 | 19 | while test $# -gt 0; do 20 | case "$1" in 21 | device) 22 | print_blue "\n ==================================== Devices: ===============================\n" 23 | adb devices 24 | print_blue "\n ==================================================================================================\n" 25 | exit 0 26 | ;; 27 | package) 28 | print_blue "\n ==================================== Packages: ===============================\n" 29 | adb shell pm list packages 30 | print_blue "\n ==================================================================================================\n" 31 | exit 0 32 | ;; 33 | 34 | shell) 35 | print_blue "\n ==================================== Device Shell: ===============================\n" 36 | adb shell 37 | print_blue "\n ==================================================================================================\n" 38 | exit 0 39 | ;; 40 | 41 | command ) 42 | print_blue "\n ==================================== ADB Commands: ===============================\n" 43 | adb shell ls /system/bin 44 | print_blue "\n ==================================================================================================\n" 45 | exit 0 46 | ;; 47 | 48 | feature ) 49 | print_blue "\n ==================================== Device Features: ===============================\n" 50 | adb shell pm list features 51 | print_blue "\n ==================================================================================================\n" 52 | exit 0 53 | ;; 54 | 55 | capture) 56 | print_blue "\n ==================================== Device ScreenShot: ===============================\n" 57 | adb shell screencap /sdcard/screenshot.png 58 | print_green "screen is captured \n" 59 | print_blue "==================================================================================================\n" 60 | exit 0 61 | ;; 62 | picture) 63 | print_blue "\n ==================================== Pull ScreenShot: ===============================\n" 64 | adb pull /sdcard/screenshot.png 65 | print_green "check your project root folder\n" 66 | print_blue "==================================================================================================\n" 67 | exit 0 68 | ;; 69 | 70 | record) 71 | print_blue "\n ==================================== Device Recording: ===============================\n" 72 | adb shell screenrecord /sdcard/record.mp4 73 | print_blue "==================================================================================================\n" 74 | exit 0 75 | ;; 76 | 77 | video) 78 | print_blue "\n ==================================== Pull Video: ===============================\n" 79 | adb pull /sdcard/record.mp4 80 | print_green "check your project root folder\n" 81 | print_blue "==================================================================================================\n" 82 | exit 0 83 | ;; 84 | 85 | anim-off) 86 | print_blue "\n ==================================== disable Animation : ===============================\n" 87 | adb shell settings put global window_animation_scale 0 88 | adb shell settings put global transition_animation_scale 0 89 | adb shell settings put global animator_duration_scale 0 90 | print_green "Animation is off\n" 91 | print_blue "==================================================================================================\n" 92 | exit 0 93 | ;; 94 | 95 | anim-on) 96 | print_blue "\n ==================================== enable Animation : ===============================\n" 97 | adb shell settings put global window_animation_scale 1 98 | adb shell settings put global transition_animation_scale 1 99 | adb shell settings put global animator_duration_scale 1 100 | print_green "Animation is on\n" 101 | print_blue "==================================================================================================\n" 102 | exit 0 103 | ;; 104 | 105 | clear) 106 | print_blue "\n ==================================== clear App Data : ===============================\n" 107 | adb shell pm clear $PACKAGE 108 | print_green "App Data is Cleared \n" 109 | print_blue "==================================================================================================\n" 110 | exit 0 111 | ;; 112 | 113 | ip) 114 | print_blue "\n ==================================== IP Address Info : ===============================\n" 115 | adb shell ip addr show 116 | print_blue "==================================================================================================\n" 117 | exit 0 118 | ;; 119 | 120 | dumpsys) 121 | print_blue "\n ==================================== Dumpsys starting : ===============================\n" 122 | adb shell dumpsys > ~/Desktop/dumpsys.txt 123 | print_green "Dumpsys is generated , check your Desktop \n" 124 | print_blue "==================================================================================================\n" 125 | exit 0 126 | ;; 127 | 128 | dumpsys-less) 129 | print_blue "\n ==================================== Dumpsys starting : ===============================\n" 130 | adb shell dumpsys | less 131 | print_blue "==================================================================================================\n" 132 | exit 0 133 | ;; 134 | 135 | battery) 136 | print_blue "\n ==================================== Battery Info : ===============================\n" 137 | adb shell dumpsys battery 138 | print_blue "==================================================================================================\n" 139 | exit 0 140 | ;; 141 | uninstall) 142 | print_blue "\n ==================================== Uninstalling : ===============================\n" 143 | adb uninstall $PACKAGE 144 | print_green "uninstall is done \n" 145 | print_blue "==================================================================================================\n" 146 | exit 0 147 | ;; 148 | 149 | *) 150 | print_red "command is not supported \n" 151 | break 152 | ;; 153 | esac 154 | done -------------------------------------------------------------------------------- /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 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/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 24 08:25:16 EET 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "Restaurants" 2 | 3 | include ':test-utils' 4 | include ':app' 5 | include ':theme' 6 | include ':settings' 7 | -------------------------------------------------------------------------------- /settings/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /settings/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | } 6 | 7 | android { 8 | compileSdkVersion 31 9 | 10 | defaultConfig { 11 | minSdkVersion 16 12 | targetSdkVersion 31 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles "consumer-rules.pro" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | kotlinOptions { 28 | jvmTarget = '1.8' 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation project(":theme") 34 | implementation Libs.hiltAndroid 35 | kapt Libs.hiltAndroidCompiler 36 | implementation Libs.fragmentKtx 37 | implementation 'androidx.core:core-ktx:1.6.0' 38 | implementation 'androidx.appcompat:appcompat:1.3.1' 39 | implementation 'com.google.android.material:material:1.4.0' 40 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 41 | implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' 42 | implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' 43 | testImplementation 'junit:junit:4.+' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 46 | } -------------------------------------------------------------------------------- /settings/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrabelwahed/AndroidTemplate/f2f732278a2eae68029056341b50733a2ba59682/settings/consumer-rules.pro -------------------------------------------------------------------------------- /settings/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 -------------------------------------------------------------------------------- /settings/src/androidTest/java/com/ramadan/settings/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.settings 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.ramadan.settings.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /settings/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /settings/src/main/java/com/ramadan/settings/SettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.ramadan.settings 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.View 6 | import android.widget.Button 7 | import android.widget.TextView 8 | import androidx.fragment.app.activityViewModels 9 | import androidx.navigation.fragment.findNavController 10 | 11 | 12 | private const val ARG_PARAM1 = "param1" 13 | private const val ARG_PARAM2 = "param2" 14 | 15 | class SettingsFragment : Fragment(R.layout.fragment_settings) { 16 | private var param1: String? = null 17 | private var param2: String? = null 18 | private val settingsViewModel:SettingsViewModel by activityViewModels() 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | arguments?.let { 23 | param1 = it.getString(ARG_PARAM1) 24 | param2 = it.getString(ARG_PARAM2) 25 | } 26 | } 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | val changeTheme = view.findViewById