├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── ic_car.png │ │ │ │ ├── bg_circle_black.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── layout │ │ │ │ └── activity_maps.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── prudhvir3ddy │ │ │ │ └── rideshare │ │ │ │ ├── data │ │ │ │ └── network │ │ │ │ │ └── NetworkService.kt │ │ │ │ ├── RideShareApp.kt │ │ │ │ ├── ui │ │ │ │ └── maps │ │ │ │ │ ├── MapsView.kt │ │ │ │ │ ├── MapsPresenter.kt │ │ │ │ │ └── MapsActivity.kt │ │ │ │ └── utils │ │ │ │ ├── AnimationUtil.kt │ │ │ │ ├── Constants.kt │ │ │ │ ├── ViewUtils.kt │ │ │ │ ├── PermissionUtils.kt │ │ │ │ └── MapUtils.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── prudhvir3ddy │ │ │ └── rideshare │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── prudhvir3ddy │ │ └── rideshare │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── simulator ├── consumer-rules.pro ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── mindorks │ │ │ └── ridesharing │ │ │ └── simulator │ │ │ ├── WebSocketListener.kt │ │ │ ├── WebSocket.kt │ │ │ └── Simulator.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── mindorks │ │ │ └── ridesharing │ │ │ └── simulator │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── mindorks │ │ └── ridesharing │ │ └── simulator │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea └── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── gradle.properties ├── .gitignore ├── gradlew.bat ├── gradlew └── README.md /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /simulator/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /simulator/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='RideShare' 2 | include ':app' 3 | include ':simulator' -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/drawable/ic_car.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prudhvir3ddy/RideShare/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /simulator/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Apr 16 15:49:55 IST 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-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | #80DCDCDC 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_circle_black.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /simulator/src/main/java/com/mindorks/ridesharing/simulator/WebSocketListener.kt: -------------------------------------------------------------------------------- 1 | package com.mindorks.ridesharing.simulator 2 | 3 | interface WebSocketListener { 4 | 5 | fun onConnect() 6 | 7 | fun onMessage(data: String) 8 | 9 | fun onDisconnect() 10 | 11 | fun onError(error: String) 12 | 13 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/data/network/NetworkService.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare.data.network 2 | 3 | import com.mindorks.ridesharing.simulator.WebSocket 4 | import com.mindorks.ridesharing.simulator.WebSocketListener 5 | 6 | class NetworkService { 7 | 8 | fun createWebSocket(webSocketListener: WebSocketListener): WebSocket { 9 | return WebSocket(webSocketListener) 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /app/src/test/java/com/prudhvir3ddy/rideshare/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /simulator/src/test/java/com/mindorks/ridesharing/simulator/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.mindorks.ridesharing.simulator 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/RideShareApp.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare 2 | 3 | import android.app.Application 4 | import com.google.android.libraries.places.api.Places 5 | import com.google.maps.GeoApiContext 6 | import com.mindorks.ridesharing.simulator.Simulator 7 | 8 | class RideShareApp : Application() { 9 | 10 | override fun onCreate() { 11 | super.onCreate() 12 | Places.initialize(applicationContext, getString(R.string.google_maps_key)) 13 | Simulator.geoApiContext = GeoApiContext.Builder() 14 | .apiKey(getString(R.string.google_maps_key)) 15 | .build() 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/ui/maps/MapsView.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare.ui.maps 2 | 3 | import com.google.android.gms.maps.model.LatLng 4 | 5 | interface MapsView { 6 | 7 | fun showNearbyCabs(latLngList: List) 8 | 9 | fun informCabBooked() 10 | 11 | fun showPath(latLngList: List) 12 | 13 | fun updateCabLocation(latLng: LatLng) 14 | 15 | fun informCabIsArriving() 16 | 17 | fun informCabArrived() 18 | 19 | fun informTripStart() 20 | 21 | fun informTripEnd() 22 | 23 | fun showRoutesNotAvailableError() 24 | 25 | fun showDirectionApiFailedError(error: String) 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/utils/AnimationUtil.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare.utils 2 | 3 | import android.animation.ValueAnimator 4 | import android.view.animation.LinearInterpolator 5 | 6 | object AnimationUtils { 7 | 8 | fun polyLineAnimator(): ValueAnimator { 9 | val valueAnimator = ValueAnimator.ofInt(0, 100) 10 | valueAnimator.interpolator = LinearInterpolator() 11 | valueAnimator.duration = 2000 12 | return valueAnimator 13 | } 14 | 15 | fun cabAnimator(): ValueAnimator { 16 | val valueAnimator = ValueAnimator.ofFloat(0f, 1f) 17 | valueAnimator.duration = 3000 18 | valueAnimator.interpolator = LinearInterpolator() 19 | return valueAnimator 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/prudhvir3ddy/rideshare/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.prudhvir3ddy.rideshare", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /simulator/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /simulator/src/androidTest/java/com/mindorks/ridesharing/simulator/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.mindorks.ridesharing.simulator 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.mindorks.ridesharing.simulator.test", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare.utils 2 | 3 | object Constants { 4 | const val TRIP_PATH = "tripPath" 5 | const val REQUEST_CAB = "requestCab" 6 | const val PICK_UP_LAT = "pickUpLat" 7 | const val PICK_UP_LNG = "pickUpLng" 8 | const val DROP_LAT = "dropLat" 9 | const val DROP_LNG = "dropLng" 10 | const val CAB_BOOKED = "cabBooked" 11 | const val PICK_UP_PATH = "pickUpPath" 12 | const val TYPE = "type" 13 | const val NEAR_BY_CABS = "nearByCabs" 14 | const val LOCATIONS = "locations" 15 | const val LOCATION = "location" 16 | const val CAB_IS_ARRIVING = "cabIsArriving" 17 | const val CAB_ARRIVED = "cabArrived" 18 | const val LAT = "lat" 19 | const val LNG = "lng" 20 | const val TRIP_START = "tripStart" 21 | const val TRIP_END = "tripEnd" 22 | const val ROUTES_NOT_AVAILABLE = "routesNotAvailable" 23 | const val DIRECTION_API_FAILED = "directionApiFailed" 24 | const val ERROR = "error" 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/utils/ViewUtils.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare.utils 2 | 3 | import android.content.res.Resources 4 | import android.os.Build 5 | import android.view.View 6 | import android.view.Window 7 | import android.view.WindowManager 8 | import kotlin.math.roundToInt 9 | 10 | object ViewUtils { 11 | 12 | fun pxToDp(px: Float): Float { 13 | val densityDpi = Resources.getSystem().displayMetrics.densityDpi 14 | return px / (densityDpi / 160f) 15 | } 16 | 17 | fun dpToPx(dp: Float): Int { 18 | val density = Resources.getSystem().displayMetrics.density 19 | return (dp * density).roundToInt() 20 | } 21 | 22 | fun enableTransparentStatusBar(window: Window) { 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 24 | val winParams = window.attributes 25 | winParams.flags = 26 | winParams.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS.inv() 27 | window.attributes = winParams 28 | window.decorView.systemUiVisibility = 29 | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Ride Share 3 | Enable GPS 4 | Required for this app 5 | Enable Now 6 | Location Permission not granted 7 | Where to? 8 | Pickup Location 9 | Current Location 10 | Request Cab 11 | Requesting Your Cab 12 | Your Cab is Booked 13 | Your Cab has Arrived 14 | Your Cab Is Arriving 15 | Route Not Available Choose Different Locations 16 | Trip End 17 | You Are On A Trip 18 | Take Next Ride 19 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /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=-Xmx1536m 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 22 | -------------------------------------------------------------------------------- /simulator/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 29 7 | buildToolsVersion "29.0.3" 8 | 9 | defaultConfig { 10 | minSdkVersion 21 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles 'consumer-rules.pro' 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 31 | implementation 'androidx.appcompat:appcompat:1.1.0' 32 | implementation 'androidx.core:core-ktx:1.2.0' 33 | // Google Maps Services (needed for directions) 34 | api 'com.google.maps:google-maps-services:0.2.9' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 38 | } 39 | -------------------------------------------------------------------------------- /simulator/src/main/java/com/mindorks/ridesharing/simulator/WebSocket.kt: -------------------------------------------------------------------------------- 1 | package com.mindorks.ridesharing.simulator 2 | 3 | import com.google.maps.model.LatLng 4 | import org.json.JSONObject 5 | 6 | class WebSocket(private var webSocketListener: WebSocketListener) { 7 | 8 | fun connect() { 9 | webSocketListener.onConnect() 10 | } 11 | 12 | fun sendMessage(data: String) { 13 | val jsonObject = JSONObject(data) 14 | when (jsonObject.getString("type")) { 15 | "nearByCabs" -> { 16 | Simulator.getFakeNearbyCabLocations( 17 | jsonObject.getDouble("lat"), 18 | jsonObject.getDouble("lng"), 19 | webSocketListener 20 | ) 21 | } 22 | "requestCab" -> { 23 | val pickUpLatLng = 24 | LatLng(jsonObject.getDouble("pickUpLat"), jsonObject.getDouble("pickUpLng")) 25 | val dropLatLng = 26 | LatLng(jsonObject.getDouble("dropLat"), jsonObject.getDouble("dropLng")) 27 | Simulator.requestCab( 28 | pickUpLatLng, 29 | dropLatLng, 30 | webSocketListener 31 | ) 32 | } 33 | } 34 | } 35 | 36 | fun disconnect() { 37 | Simulator.stopTimer() 38 | this.webSocketListener.onDisconnect() 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | def localProperties = new Properties() 6 | localProperties.load(new FileInputStream(rootProject.file("local.properties"))) 7 | 8 | android { 9 | compileSdkVersion 29 10 | buildToolsVersion "29.0.3" 11 | 12 | defaultConfig { 13 | applicationId "com.prudhvir3ddy.rideshare" 14 | minSdkVersion 21 15 | targetSdkVersion 29 16 | versionCode 1 17 | versionName "1.0" 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | debug { 23 | resValue("string", "google_maps_key", localProperties['apiKey']) 24 | } 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | } 31 | 32 | dependencies { 33 | implementation fileTree(dir: 'libs', include: ['*.jar']) 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 35 | implementation 'androidx.appcompat:appcompat:1.1.0' 36 | implementation 'androidx.core:core-ktx:1.2.0' 37 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 38 | testImplementation 'junit:junit:4.12' 39 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 41 | implementation project(':simulator') 42 | // Places Library (required for search places) 43 | implementation 'com.google.android.libraries.places:places:2.2.0' 44 | implementation 'com.google.android.gms:play-services-maps:17.0.0' 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/utils/PermissionUtils.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare.utils 2 | 3 | import android.Manifest 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import android.location.LocationManager 8 | import android.provider.Settings 9 | import androidx.appcompat.app.AlertDialog 10 | import androidx.appcompat.app.AppCompatActivity 11 | import androidx.core.app.ActivityCompat 12 | import androidx.core.content.ContextCompat 13 | import com.prudhvir3ddy.rideshare.R 14 | 15 | object PermissionUtils { 16 | 17 | fun requestAccessFineLocationPermission(activity: AppCompatActivity, requestId: Int) { 18 | ActivityCompat.requestPermissions( 19 | activity, 20 | arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 21 | requestId 22 | ) 23 | } 24 | 25 | fun isAccessFineLocationGranted(context: Context): Boolean { 26 | return ContextCompat 27 | .checkSelfPermission( 28 | context, 29 | Manifest.permission.ACCESS_FINE_LOCATION 30 | ) == PackageManager.PERMISSION_GRANTED 31 | } 32 | 33 | fun isLocationEnabled(context: Context): Boolean { 34 | val locationManager: LocationManager = 35 | context.getSystemService(Context.LOCATION_SERVICE) as LocationManager 36 | return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) 37 | || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) 38 | } 39 | 40 | fun showGPSNotEnabledDialog(context: Context) { 41 | AlertDialog.Builder(context) 42 | .setTitle(context.getString(R.string.enable_gps)) 43 | .setMessage(context.getString(R.string.required_for_this_app)) 44 | .setCancelable(false) 45 | .setPositiveButton(context.getString(R.string.enable_now)) { _, _ -> 46 | context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) 47 | } 48 | .show() 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/utils/MapUtils.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare.utils 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.BitmapFactory 6 | import android.graphics.Canvas 7 | import android.graphics.Color 8 | import android.graphics.Paint 9 | import com.google.android.gms.maps.model.LatLng 10 | import com.prudhvir3ddy.rideshare.R 11 | import kotlin.math.abs 12 | import kotlin.math.atan 13 | 14 | object MapUtils { 15 | 16 | fun getCarBitmap(context: Context): Bitmap { 17 | val bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.ic_car) 18 | return Bitmap.createScaledBitmap(bitmap, 50, 100, false) 19 | } 20 | fun getDestinationBitmap(): Bitmap { 21 | val height = 20 22 | val width = 20 23 | val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565) 24 | val canvas = Canvas(bitmap) 25 | val paint = Paint() 26 | paint.color = Color.BLACK 27 | paint.style = Paint.Style.FILL 28 | paint.isAntiAlias = true 29 | canvas.drawRect(0F, 0F, width.toFloat(), height.toFloat(), paint) 30 | return bitmap 31 | } 32 | 33 | fun getRotation(start: LatLng, end: LatLng): Float { 34 | val latDifference: Double = abs(start.latitude - end.latitude) 35 | val lngDifference: Double = abs(start.longitude - end.longitude) 36 | var rotation = -1F 37 | when { 38 | start.latitude < end.latitude && start.longitude < end.longitude -> { 39 | rotation = Math.toDegrees(atan(lngDifference / latDifference)).toFloat() 40 | } 41 | start.latitude >= end.latitude && start.longitude < end.longitude -> { 42 | rotation = (90 - Math.toDegrees(atan(lngDifference / latDifference)) + 90).toFloat() 43 | } 44 | start.latitude >= end.latitude && start.longitude >= end.longitude -> { 45 | rotation = (Math.toDegrees(atan(lngDifference / latDifference)) + 180).toFloat() 46 | } 47 | start.latitude < end.latitude && start.longitude >= end.longitude -> { 48 | rotation = 49 | (90 - Math.toDegrees(atan(lngDifference / latDifference)) + 270).toFloat() 50 | } 51 | } 52 | return rotation 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/ui/maps/MapsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare.ui.maps 2 | 3 | import android.util.Log 4 | import com.google.android.gms.maps.model.LatLng 5 | import com.mindorks.ridesharing.simulator.WebSocket 6 | import com.mindorks.ridesharing.simulator.WebSocketListener 7 | import com.prudhvir3ddy.rideshare.data.network.NetworkService 8 | import com.prudhvir3ddy.rideshare.utils.Constants 9 | import org.json.JSONObject 10 | 11 | class MapsPresenter( 12 | private val networkService: NetworkService 13 | ) : WebSocketListener { 14 | 15 | companion object { 16 | private const val TAG = "MapsPresenter" 17 | } 18 | 19 | private var view: MapsView? = null 20 | private lateinit var webSocket: WebSocket 21 | 22 | fun onAttach(view: MapsView?) { 23 | this.view = view 24 | webSocket = networkService.createWebSocket(this) 25 | webSocket.connect() 26 | } 27 | 28 | fun onDetach() { 29 | webSocket.disconnect() 30 | view = null 31 | } 32 | 33 | override fun onConnect() { 34 | Log.d(TAG, "onConnect") 35 | } 36 | 37 | override fun onMessage(data: String) { 38 | Log.d(TAG, "onMessage data : $data") 39 | val jsonObject = JSONObject(data) 40 | when (jsonObject.getString(Constants.TYPE)) { 41 | Constants.NEAR_BY_CABS -> { 42 | handleOnMessageNearbyCabs(jsonObject) 43 | } 44 | Constants.CAB_BOOKED -> { 45 | view?.informCabBooked() 46 | } 47 | Constants.PICK_UP_PATH, Constants.TRIP_PATH -> { 48 | val jsonArray = jsonObject.getJSONArray("path") 49 | val pickUpPath = arrayListOf() 50 | for (i in 0 until jsonArray.length()) { 51 | val lat = (jsonArray.get(i) as JSONObject).getDouble("lat") 52 | val lng = (jsonArray.get(i) as JSONObject).getDouble("lng") 53 | val latLng = LatLng(lat, lng) 54 | pickUpPath.add(latLng) 55 | } 56 | view?.showPath(pickUpPath) 57 | } 58 | Constants.LOCATION -> { 59 | val latCurrent = jsonObject.getDouble("lat") 60 | val lngCurrent = jsonObject.getDouble("lng") 61 | view?.updateCabLocation(LatLng(latCurrent, lngCurrent)) 62 | } 63 | Constants.CAB_ARRIVED -> { 64 | view?.informCabArrived() 65 | } 66 | Constants.CAB_IS_ARRIVING -> { 67 | view?.informCabIsArriving() 68 | } 69 | Constants.TRIP_START -> { 70 | view?.informTripStart() 71 | } 72 | Constants.TRIP_END -> { 73 | view?.informTripEnd() 74 | } 75 | } 76 | } 77 | 78 | override fun onDisconnect() { 79 | Log.d(TAG, "onDisconnect") 80 | } 81 | 82 | override fun onError(error: String) { 83 | Log.d(TAG, "onError : $error") 84 | val jsonObject = JSONObject(error) 85 | when (jsonObject.getString(Constants.TYPE)) { 86 | Constants.ROUTES_NOT_AVAILABLE -> { 87 | view?.showRoutesNotAvailableError() 88 | } 89 | Constants.DIRECTION_API_FAILED -> { 90 | view?.showDirectionApiFailedError( 91 | "Direction API Failed : " + jsonObject.getString( 92 | Constants.ERROR 93 | ) 94 | ) 95 | } 96 | } 97 | } 98 | 99 | private fun handleOnMessageNearbyCabs(jsonObject: JSONObject) { 100 | val nearbyCabLocations = arrayListOf() 101 | val jsonArray = jsonObject.getJSONArray(Constants.LOCATIONS) 102 | for (i in 0 until jsonArray.length()) { 103 | val lat = (jsonArray.get(i) as JSONObject).getDouble(Constants.LAT) 104 | val lng = (jsonArray.get(i) as JSONObject).getDouble(Constants.LNG) 105 | val latLng = LatLng(lat, lng) 106 | nearbyCabLocations.add(latLng) 107 | } 108 | view?.showNearbyCabs(nearbyCabLocations) 109 | } 110 | 111 | fun requestNearbyCabs(latLng: LatLng) { 112 | val jsonObject = JSONObject() 113 | jsonObject.put(Constants.TYPE, Constants.NEAR_BY_CABS) 114 | jsonObject.put(Constants.LAT, latLng.latitude) 115 | jsonObject.put(Constants.LNG, latLng.longitude) 116 | webSocket.sendMessage(jsonObject.toString()) 117 | } 118 | 119 | fun requestCab(pickUpLatLng: LatLng, dropLatLng: LatLng) { 120 | val jsonObject = JSONObject() 121 | jsonObject.put(Constants.TYPE, Constants.REQUEST_CAB) 122 | jsonObject.put(Constants.PICK_UP_LAT, pickUpLatLng.latitude) 123 | jsonObject.put(Constants.PICK_UP_LNG, pickUpLatLng.longitude) 124 | jsonObject.put(Constants.DROP_LAT, dropLatLng.latitude) 125 | jsonObject.put(Constants.DROP_LNG, dropLatLng.longitude) 126 | webSocket.sendMessage(jsonObject.toString()) 127 | } 128 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/layout/activity_maps.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 19 | 31 | 32 | 42 | 43 | 65 | 66 | 75 | 76 | 86 | 87 | 111 | 112 | 113 | 134 | 135 | 151 | 152 | 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Live Tracking Vechile Animation Android App With Google Maps 2 | ##### Built this app from mindorks android course 3 | 4 | Learn to build a ride-sharing Android Taxi Clone App like Uber, Lyft - Open-Source Project By MindOrks 5 | 6 | [![MindOrks](https://img.shields.io/badge/mindorks-opensource-blue.svg)](https://mindorks.com/open-source-projects) 7 | [![MindOrks Community](https://img.shields.io/badge/join-community-blue.svg)](https://mindorks.com/join-community) 8 | 9 |

10 | 11 |

12 |
13 | 14 | ## About this Open Source Project 15 | This open-source project is for you(community). Our Team at [MindOrks](https://mindorks.com) has taken this initiative to promote Android Learning in the best possible way. We are determined to provide quality content for everyone. Let's do it together by learning from this project. 16 | 17 | ## We will build and learn the following for the App like Uber and Lyft: 18 | * Create Rider Android Clone App 19 | * Fetch and show nearby cabs on Google Map 20 | * Set Pickup and drop location 21 | * Book a cab 22 | * Fetch and show driver current location 23 | * Show pickup and trip path on Map with Animation 24 | * Cab Arrival for a pickup like Uber 25 | * On-going trip UI 26 | * Trip End 27 | * Animation like Uber App for Moving Car 28 | * Just to make it simple. This project uses the basic MVP Architecture for building the Uber and Lyft clone 29 | * We have simulated the WebSocket API for you 30 | 31 | ## We have simulated the backend environment for you to get the real-work like experience. 32 | 33 | ## Video showing the App demo with Car Animation, [check here](https://www.youtube.com/watch?v=xn3BOf8uOgc) 34 | 35 |

36 | 37 |

38 |
39 | 40 | ## Screenshots from this project 41 | 42 |

43 | 44 | 45 | 46 | 47 |

48 |
49 |

50 | 51 | 52 | 53 | 54 |

55 | 56 | ## Building the project 57 | * Every feature is done in a different branch so that it will be easy to follow. 58 | * Clone the project, the `master` branch has the latest code. 59 | * To learn and implement from the beginning, switch the branch to `base-project` 60 | * This App uses the Google API Key for Maps, Directions, and Places. Get the API key from the Google Cloud Developer console after enabling the Maps, Directions and Places features for your project. Refer this [link](https://developers.google.com/maps/documentation/directions/get-api-key). And put that key in the local.properties file in your project: 61 | Your local.properties will like below: 62 | ``` 63 | sdk.dir=PATH_TO_ANDROID_SDK_ON_YOUR_LOCAL_MACHINE 64 | apiKey=YOUR_API_KEY 65 | ``` 66 | * Start implementing features: 67 | * Start with the `base-project` branch 68 | * Setup project with basic MVP Architecture. 69 | * Resource to Learn: [Basic Android MVP Introduction](https://mindorks.com/course/android-mvp-introduction) 70 | * Implement Permission for fetching current location. 71 | * Resource to Learn: [Using Fused Location API To Fetch Current Location](https://blog.mindorks.com/using-gps-location-manager-in-android-android-tutorial) 72 | * Implement feature - nearby cabs. 73 | * Use WebSocket present in `simulator` module to fetch the nearby cabs. 74 | * Match your solution with `nearby-cabs` branch. 75 | * Implement feature - pickup and drop location. 76 | * Match your solution with `pickup-drop-location` branch. 77 | * Implement feature - book a cab. 78 | * Implement feature - Show pickup path on the map with Animation. 79 | * Resource to Learn - Blog: [How to Add Uber Car Animation in Android App?](https://blog.mindorks.com/how-to-add-uber-car-animation-in-android-app) 80 | * Resource to Learn - Project: [Uber-Car-Animation-Android](https://github.com/MindorksOpenSource/Uber-Car-Animation-Android) 81 | * Implement feature - Show the current driver location during pickup. 82 | * Implement feature - Cab is arriving and arrived. 83 | * Implement feature - Car Animation like Uber. 84 | * Implement feature - Show trip path on the map with Animation. 85 | * Implement feature - Trip Starts. 86 | * Implement feature - Show the current driver location during the trip. 87 | * Implement feature - Trip on-going. 88 | * Implement feature - Trip Ends. 89 | * Implement feature - Implement Take Next Ride. 90 | * Match your solution with `book-complete-trip` branch. 91 | 92 | ## Explore Android Online Tutorials and Courses To Learn More by MindOrks 93 | * [Android Tutorial](https://mindorks.com/android-tutorial) - All Free Android Tutorials by MindOrks 94 | * [Android Online Course for Professionals](https://bootcamp.mindorks.com) - In this online course, you’ll learn the Dagger, Kotlin, RxJava, MVVM Architecture, Architecture Components, Jetpack, LiveData, ViewModel, Room Database, Database Design, Multithreading, Memory Management, Networking, Caching, How Glide works, Unit Testing, and the best practices for Android Development. By the end of this online course, you will have all the skills you need to become a professional Android Developer. 95 | * [Android Online Course for Beginners](https://bootcamp.mindorks.com/android-training-for-beginners) - This course is for beginners for those who want to get started with Android Development. In this course, you will build two apps: TodoNotes and Ride-Sharing Uber Android App. 96 | 97 | ## WebSocket API Reference for this project 98 | A WebSocket is a persistent connection between a client and server. WebSockets provide a bidirectional, full-duplex communications channel that operates over HTTP through a single TCP/IP socket connection. At its core, the WebSocket protocol facilitates message passing between a client and server. In our case, we have simulated it for you. 99 | 100 | * In WebSocket, we have three methods: 101 | * `connect()`: To connect with the server 102 | * `sendMessage(data: String)`: To send the data to the server 103 | * `disconnect()`: To disconnect from the server 104 | 105 | * In WebSocketListener, we have four callbacks: 106 | * `onConnect()`: Called when it is connected with the server 107 | * `onMessage(data: String)`: Called when an event comes from the server 108 | * `fun onDisconnect()`: Called when the client is disconnected from the server 109 | * `fun onError(error: String)`: Called when the error occurred on the server 110 | 111 | * Client sending event to server using `webSocket.sendMessage(data)`: 112 | * Request for nearby cabs from server 113 | ```json 114 | { 115 | "type": "nearByCabs", 116 | "lat": 28.438147, 117 | "lng": 77.0994446 118 | } 119 | ``` 120 | * Request a cab from server 121 | ```json 122 | { 123 | "type": "requestCab", 124 | "pickUpLat": 28.4369353, 125 | "pickUpLng": 77.1125599, 126 | "dropLat": -25.274398, 127 | "dropLng": 133.775136 128 | } 129 | ``` 130 | 131 | * The Server sending success event to the client received in `onMessage(data: String)`: 132 | * NearBy cabs 133 | ```json 134 | { 135 | "type": "nearByCabs", 136 | "locations": [ 137 | { 138 | "lat": 28.439147000000002, 139 | "lng": 77.0944446 140 | }, 141 | { 142 | "lat": 28.433147, 143 | "lng": 77.0952446 144 | }, 145 | { 146 | "lat": 28.440547000000002, 147 | "lng": 77.1026446 148 | } 149 | ] 150 | } 151 | ``` 152 | * Cab Booked 153 | ```json 154 | { 155 | "type": "cabBooked" 156 | } 157 | ``` 158 | * PickUp Path 159 | ```json 160 | { 161 | "type": "pickUpPath", 162 | "path": [ 163 | { 164 | "lat": 28.43578, 165 | "lng": 77.10198000000001 166 | }, 167 | { 168 | "lat": 28.43614, 169 | "lng": 77.10164 170 | }, 171 | { 172 | "lat": 28.436400000000003, 173 | "lng": 77.10149000000001 174 | } 175 | ] 176 | } 177 | ``` 178 | * Cab Current Location during pickup or trip 179 | ```json 180 | { 181 | "type": "location", 182 | "lat": 28.43578, 183 | "lng": 77.10198000000001 184 | } 185 | ``` 186 | * Cab is Arriving 187 | ```json 188 | { 189 | "type": "cabIsArriving" 190 | } 191 | ``` 192 | * Cab Arrived 193 | ```json 194 | { 195 | "type": "cabArrived" 196 | } 197 | ``` 198 | * Trip Start 199 | ```json 200 | { 201 | "type": "tripStart" 202 | } 203 | ``` 204 | * Trip Path 205 | ```json 206 | { 207 | "type": "tripPath", 208 | "path": [ 209 | { 210 | "lat": 28.438370000000003, 211 | "lng": 77.09944 212 | }, 213 | { 214 | "lat": 28.438450000000003, 215 | "lng": 77.1006 216 | }, 217 | { 218 | "lat": 28.438480000000002, 219 | "lng": 77.10095000000001 220 | } 221 | ] 222 | } 223 | ``` 224 | * Trip End 225 | ```json 226 | { 227 | "type": "tripEnd" 228 | } 229 | ``` 230 | * The server sending the error event to the client received in `onError(error: String)`: 231 | * Direction API Failed 232 | ```json 233 | { 234 | "type": "directionApiFailed", 235 | "error": "Unable to resolve host \"maps.googleapis.com\": No address associated with hostname" 236 | } 237 | ``` 238 | * Routes Not Available 239 | ```json 240 | { 241 | "type": "routesNotAvailable" 242 | } 243 | ``` 244 | 245 | ### Find this project useful ? :heart: 246 | 247 | * Support it by clicking the :star: button on the upper right of this page. :v: 248 | 249 | ### License 250 | ``` 251 | Copyright (C) 2020 MINDORKS NEXTGEN PRIVATE LIMITED 252 | 253 | Licensed under the Apache License, Version 2.0 (the "License"); 254 | you may not use this file except in compliance with the License. 255 | You may obtain a copy of the License at 256 | 257 | http://www.apache.org/licenses/LICENSE-2.0 258 | 259 | Unless required by applicable law or agreed to in writing, software 260 | distributed under the License is distributed on an "AS IS" BASIS, 261 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 262 | See the License for the specific language governing permissions and 263 | limitations under the License. 264 | ``` 265 | -------------------------------------------------------------------------------- /simulator/src/main/java/com/mindorks/ridesharing/simulator/Simulator.kt: -------------------------------------------------------------------------------- 1 | package com.mindorks.ridesharing.simulator 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import android.util.Log 6 | import com.google.maps.DirectionsApiRequest 7 | import com.google.maps.GeoApiContext 8 | import com.google.maps.PendingResult 9 | import com.google.maps.model.DirectionsResult 10 | import com.google.maps.model.LatLng 11 | import com.google.maps.model.TravelMode 12 | import org.json.JSONArray 13 | import org.json.JSONObject 14 | import java.util.* 15 | 16 | object Simulator { 17 | 18 | private const val TAG = "Simulator" 19 | private var timer: Timer? = null 20 | private var timerTask: TimerTask? = null 21 | lateinit var geoApiContext: GeoApiContext 22 | private lateinit var currentLocation: LatLng 23 | private lateinit var pickUpLocation: LatLng 24 | private lateinit var dropLocation: LatLng 25 | private var nearbyCabLocations = arrayListOf() 26 | private var pickUpPath = arrayListOf() 27 | private var tripPath = arrayListOf() 28 | private val mainThread = Handler(Looper.getMainLooper()) 29 | 30 | fun getFakeNearbyCabLocations( 31 | latitude: Double, 32 | longitude: Double, 33 | webSocketListener: WebSocketListener 34 | ) { 35 | nearbyCabLocations.clear() 36 | currentLocation = LatLng(latitude, longitude) 37 | val size = (4..6).random() 38 | 39 | for (i in 1..size) { 40 | val randomOperatorForLat = (0..1).random() 41 | val randomOperatorForLng = (0..1).random() 42 | var randomDeltaForLat = (10..50).random() / 10000.00 43 | var randomDeltaForLng = (10..50).random() / 10000.00 44 | if (randomOperatorForLat == 1) { 45 | randomDeltaForLat *= -1 46 | } 47 | if (randomOperatorForLng == 1) { 48 | randomDeltaForLng *= -1 49 | } 50 | val randomLatitude = (latitude + randomDeltaForLat).coerceAtMost(90.00) 51 | val randomLongitude = (longitude + randomDeltaForLng).coerceAtMost(180.00) 52 | nearbyCabLocations.add(LatLng(randomLatitude, randomLongitude)) 53 | } 54 | 55 | val jsonObjectToPush = JSONObject() 56 | jsonObjectToPush.put("type", "nearByCabs") 57 | val jsonArray = JSONArray() 58 | for (location in nearbyCabLocations) { 59 | val jsonObjectLatLng = JSONObject() 60 | jsonObjectLatLng.put("lat", location.lat) 61 | jsonObjectLatLng.put("lng", location.lng) 62 | jsonArray.put(jsonObjectLatLng) 63 | } 64 | jsonObjectToPush.put("locations", jsonArray) 65 | mainThread.post { 66 | webSocketListener.onMessage(jsonObjectToPush.toString()) 67 | } 68 | } 69 | 70 | fun requestCab( 71 | pickUpLocation: LatLng, 72 | dropLocation: LatLng, 73 | webSocketListener: WebSocketListener 74 | ) { 75 | this.pickUpLocation = pickUpLocation 76 | this.dropLocation = dropLocation 77 | 78 | val randomOperatorForLat = (0..1).random() 79 | val randomOperatorForLng = (0..1).random() 80 | 81 | var randomDeltaForLat = (5..30).random() / 10000.00 82 | var randomDeltaForLng = (5..30).random() / 10000.00 83 | 84 | if (randomOperatorForLat == 1) { 85 | randomDeltaForLat *= -1 86 | } 87 | if (randomOperatorForLng == 1) { 88 | randomDeltaForLng *= -1 89 | } 90 | val latFakeNearby = (pickUpLocation.lat + randomDeltaForLat).coerceAtMost(90.00) 91 | val lngFakeNearby = (pickUpLocation.lng + randomDeltaForLng).coerceAtMost(180.00) 92 | 93 | val bookedCabCurrentLocation = LatLng(latFakeNearby, lngFakeNearby) 94 | val directionsApiRequest = DirectionsApiRequest(geoApiContext) 95 | directionsApiRequest.mode(TravelMode.DRIVING) 96 | directionsApiRequest.origin(bookedCabCurrentLocation) 97 | directionsApiRequest.destination(this.pickUpLocation) 98 | directionsApiRequest.setCallback(object : PendingResult.Callback { 99 | override fun onResult(result: DirectionsResult) { 100 | Log.d(TAG, "onResult : $result") 101 | val jsonObjectCabBooked = JSONObject() 102 | jsonObjectCabBooked.put("type", "cabBooked") 103 | mainThread.post { 104 | webSocketListener.onMessage(jsonObjectCabBooked.toString()) 105 | } 106 | pickUpPath.clear() 107 | val routeList = result.routes 108 | // Actually it will have zero or 1 route as we haven't asked Google API for multiple paths 109 | 110 | if (routeList.isEmpty()) { 111 | val jsonObjectFailure = JSONObject() 112 | jsonObjectFailure.put("type", "routesNotAvailable") 113 | mainThread.post { 114 | webSocketListener.onError(jsonObjectFailure.toString()) 115 | } 116 | } else { 117 | for (route in routeList) { 118 | val path = route.overviewPolyline.decodePath() 119 | pickUpPath.addAll(path) 120 | } 121 | 122 | val jsonObject = JSONObject() 123 | jsonObject.put("type", "pickUpPath") 124 | val jsonArray = JSONArray() 125 | for (pickUp in pickUpPath) { 126 | val jsonObjectLatLng = JSONObject() 127 | jsonObjectLatLng.put("lat", pickUp.lat) 128 | jsonObjectLatLng.put("lng", pickUp.lng) 129 | jsonArray.put(jsonObjectLatLng) 130 | } 131 | jsonObject.put("path", jsonArray) 132 | mainThread.post { 133 | webSocketListener.onMessage(jsonObject.toString()) 134 | } 135 | 136 | startTimerForPickUp(webSocketListener) 137 | } 138 | 139 | 140 | } 141 | 142 | override fun onFailure(e: Throwable) { 143 | Log.d(TAG, "onFailure : ${e.message}") 144 | val jsonObjectFailure = JSONObject() 145 | jsonObjectFailure.put("type", "directionApiFailed") 146 | jsonObjectFailure.put("error", e.message) 147 | mainThread.post { 148 | webSocketListener.onError(jsonObjectFailure.toString()) 149 | } 150 | } 151 | }) 152 | } 153 | 154 | fun startTimerForPickUp(webSocketListener: WebSocketListener) { 155 | val delay = 2000L 156 | val period = 3000L 157 | val size = pickUpPath.size 158 | var index = 0 159 | timer = Timer() 160 | timerTask = object : TimerTask() { 161 | override fun run() { 162 | val jsonObject = JSONObject() 163 | jsonObject.put("type", "location") 164 | jsonObject.put("lat", pickUpPath[index].lat) 165 | jsonObject.put("lng", pickUpPath[index].lng) 166 | mainThread.post { 167 | webSocketListener.onMessage(jsonObject.toString()) 168 | } 169 | 170 | if (index == size - 1) { 171 | stopTimer() 172 | val jsonObjectCabIsArriving = JSONObject() 173 | jsonObjectCabIsArriving.put("type", "cabIsArriving") 174 | mainThread.post { 175 | webSocketListener.onMessage(jsonObjectCabIsArriving.toString()) 176 | } 177 | startTimerForWaitDuringPickUp(webSocketListener) 178 | } 179 | 180 | index++ 181 | } 182 | } 183 | 184 | timer?.schedule(timerTask, delay, period) 185 | } 186 | 187 | fun startTimerForWaitDuringPickUp(webSocketListener: WebSocketListener) { 188 | val delay = 3000L 189 | val period = 3000L 190 | timer = Timer() 191 | timerTask = object : TimerTask() { 192 | override fun run() { 193 | stopTimer() 194 | val jsonObjectCabArrived = JSONObject() 195 | jsonObjectCabArrived.put("type", "cabArrived") 196 | mainThread.post { 197 | webSocketListener.onMessage(jsonObjectCabArrived.toString()) 198 | } 199 | val directionsApiRequest = DirectionsApiRequest(geoApiContext) 200 | directionsApiRequest.mode(TravelMode.DRIVING) 201 | directionsApiRequest.origin(pickUpLocation) 202 | directionsApiRequest.destination(dropLocation) 203 | directionsApiRequest.setCallback(object : 204 | PendingResult.Callback { 205 | override fun onResult(result: DirectionsResult) { 206 | Log.d(TAG, "onResult : $result") 207 | tripPath.clear() 208 | val routeList = result.routes 209 | // Actually it will have zero or 1 route as we haven't asked Google API for multiple paths 210 | 211 | if (routeList.isEmpty()) { 212 | val jsonObjectFailure = JSONObject() 213 | jsonObjectFailure.put("type", "routesNotAvailable") 214 | mainThread.post { 215 | webSocketListener.onError(jsonObjectFailure.toString()) 216 | } 217 | } else { 218 | for (route in routeList) { 219 | val path = route.overviewPolyline.decodePath() 220 | tripPath.addAll(path) 221 | } 222 | startTimerForTrip(webSocketListener) 223 | } 224 | 225 | } 226 | 227 | override fun onFailure(e: Throwable) { 228 | Log.d(TAG, "onFailure : ${e.message}") 229 | val jsonObjectFailure = JSONObject() 230 | jsonObjectFailure.put("type", "directionApiFailed") 231 | jsonObjectFailure.put("error", e.message) 232 | mainThread.post { 233 | webSocketListener.onError(jsonObjectFailure.toString()) 234 | } 235 | } 236 | }) 237 | 238 | } 239 | } 240 | timer?.schedule(timerTask, delay, period) 241 | } 242 | 243 | fun startTimerForTrip(webSocketListener: WebSocketListener) { 244 | val delay = 5000L 245 | val period = 3000L 246 | val size = tripPath.size 247 | var index = 0 248 | timer = Timer() 249 | timerTask = object : TimerTask() { 250 | override fun run() { 251 | 252 | if (index == 0) { 253 | val jsonObjectTripStart = JSONObject() 254 | jsonObjectTripStart.put("type", "tripStart") 255 | mainThread.post { 256 | webSocketListener.onMessage(jsonObjectTripStart.toString()) 257 | } 258 | 259 | val jsonObject = JSONObject() 260 | jsonObject.put("type", "tripPath") 261 | val jsonArray = JSONArray() 262 | for (trip in tripPath) { 263 | val jsonObjectLatLng = JSONObject() 264 | jsonObjectLatLng.put("lat", trip.lat) 265 | jsonObjectLatLng.put("lng", trip.lng) 266 | jsonArray.put(jsonObjectLatLng) 267 | } 268 | jsonObject.put("path", jsonArray) 269 | mainThread.post { 270 | webSocketListener.onMessage(jsonObject.toString()) 271 | } 272 | } 273 | 274 | val jsonObject = JSONObject() 275 | jsonObject.put("type", "location") 276 | jsonObject.put("lat", tripPath[index].lat) 277 | jsonObject.put("lng", tripPath[index].lng) 278 | mainThread.post { 279 | webSocketListener.onMessage(jsonObject.toString()) 280 | } 281 | 282 | if (index == size - 1) { 283 | stopTimer() 284 | startTimerForTripEndEvent(webSocketListener) 285 | } 286 | 287 | index++ 288 | } 289 | } 290 | timer?.schedule(timerTask, delay, period) 291 | } 292 | 293 | fun startTimerForTripEndEvent(webSocketListener: WebSocketListener) { 294 | val delay = 3000L 295 | val period = 3000L 296 | timer = Timer() 297 | timerTask = object : TimerTask() { 298 | override fun run() { 299 | stopTimer() 300 | val jsonObjectTripEnd = JSONObject() 301 | jsonObjectTripEnd.put("type", "tripEnd") 302 | mainThread.post { 303 | webSocketListener.onMessage(jsonObjectTripEnd.toString()) 304 | } 305 | } 306 | } 307 | timer?.schedule(timerTask, delay, period) 308 | } 309 | 310 | fun stopTimer() { 311 | if (timer != null) { 312 | timer?.cancel() 313 | timer = null 314 | } 315 | } 316 | 317 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prudhvir3ddy/rideshare/ui/maps/MapsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.prudhvir3ddy.rideshare.ui.maps 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.graphics.Color 7 | import android.os.Bundle 8 | import android.os.Looper 9 | import android.util.Log 10 | import android.view.View 11 | import android.widget.Toast 12 | import androidx.appcompat.app.AppCompatActivity 13 | import com.google.android.gms.common.api.Status 14 | import com.google.android.gms.location.FusedLocationProviderClient 15 | import com.google.android.gms.location.LocationCallback 16 | import com.google.android.gms.location.LocationRequest 17 | import com.google.android.gms.location.LocationResult 18 | import com.google.android.gms.maps.CameraUpdateFactory 19 | import com.google.android.gms.maps.GoogleMap 20 | import com.google.android.gms.maps.OnMapReadyCallback 21 | import com.google.android.gms.maps.SupportMapFragment 22 | import com.google.android.gms.maps.model.BitmapDescriptorFactory 23 | import com.google.android.gms.maps.model.CameraPosition 24 | import com.google.android.gms.maps.model.LatLng 25 | import com.google.android.gms.maps.model.LatLngBounds 26 | import com.google.android.gms.maps.model.Marker 27 | import com.google.android.gms.maps.model.MarkerOptions 28 | import com.google.android.gms.maps.model.Polyline 29 | import com.google.android.gms.maps.model.PolylineOptions 30 | import com.google.android.libraries.places.api.model.Place 31 | import com.google.android.libraries.places.widget.Autocomplete 32 | import com.google.android.libraries.places.widget.AutocompleteActivity 33 | import com.google.android.libraries.places.widget.model.AutocompleteActivityMode 34 | import com.prudhvir3ddy.rideshare.R 35 | import com.prudhvir3ddy.rideshare.R.string 36 | import com.prudhvir3ddy.rideshare.data.network.NetworkService 37 | import com.prudhvir3ddy.rideshare.utils.AnimationUtils 38 | import com.prudhvir3ddy.rideshare.utils.MapUtils 39 | import com.prudhvir3ddy.rideshare.utils.PermissionUtils 40 | import com.prudhvir3ddy.rideshare.utils.ViewUtils 41 | import kotlinx.android.synthetic.main.activity_maps.dropTextView 42 | import kotlinx.android.synthetic.main.activity_maps.nextRideButton 43 | import kotlinx.android.synthetic.main.activity_maps.pickUpTextView 44 | import kotlinx.android.synthetic.main.activity_maps.requestCabButton 45 | import kotlinx.android.synthetic.main.activity_maps.statusTextView 46 | 47 | class MapsActivity : AppCompatActivity(), OnMapReadyCallback, MapsView { 48 | 49 | companion object { 50 | private const val TAG = "MapsActivity" 51 | private const val LOCATION_PERMISSION_REQUEST_CODE = 999 52 | private const val PICKUP_REQUEST_CODE = 1 53 | private const val DROP_REQUEST_CODE = 2 54 | } 55 | 56 | private var movingCabMarker: Marker? = null 57 | private var destinationMarker: Marker? = null 58 | private var originMarker: Marker? = null 59 | private var blackPolyLine: Polyline? = null 60 | private var greyPolyLine: Polyline? = null 61 | private lateinit var presenter: MapsPresenter 62 | private lateinit var googleMap: GoogleMap 63 | private var fusedLocationProviderClient: FusedLocationProviderClient? = null 64 | private lateinit var locationCallback: LocationCallback 65 | private var currentLatLng: LatLng? = null 66 | private var pickUpLatLng: LatLng? = null 67 | private var dropLatLng: LatLng? = null 68 | private var previousLatLngFromServer: LatLng? = null 69 | private var currentLatLngFromServer: LatLng? = null 70 | private val nearbyCabMarkerList = arrayListOf() 71 | 72 | override fun onCreate(savedInstanceState: Bundle?) { 73 | super.onCreate(savedInstanceState) 74 | setContentView(R.layout.activity_maps) 75 | ViewUtils.enableTransparentStatusBar(window) 76 | val mapFragment = supportFragmentManager 77 | .findFragmentById(R.id.map) as SupportMapFragment 78 | mapFragment.getMapAsync(this) 79 | presenter = MapsPresenter(NetworkService()) 80 | presenter.onAttach(this) 81 | setUpClickListener() 82 | } 83 | 84 | private fun setUpClickListener() { 85 | pickUpTextView.setOnClickListener { 86 | launchLocationAutoCompleteActivity(PICKUP_REQUEST_CODE) 87 | } 88 | dropTextView.setOnClickListener { 89 | launchLocationAutoCompleteActivity(DROP_REQUEST_CODE) 90 | } 91 | requestCabButton.setOnClickListener { 92 | enableDisableViews(false) 93 | statusTextView.visibility = View.VISIBLE 94 | statusTextView.text = getString(string.requesting_your_cab) 95 | presenter.requestCab(pickUpLatLng!!, dropLatLng!!) 96 | } 97 | nextRideButton.setOnClickListener { 98 | reset() 99 | } 100 | } 101 | 102 | private fun enableDisableViews(shouldEnable: Boolean) { 103 | pickUpTextView.isEnabled = shouldEnable 104 | dropTextView.isEnabled = shouldEnable 105 | requestCabButton.isEnabled = shouldEnable 106 | } 107 | 108 | private fun checkAndShowRequestButton() { 109 | if (pickUpLatLng != null && dropLatLng != null) { 110 | requestCabButton.visibility = View.VISIBLE 111 | requestCabButton.isEnabled = true 112 | } 113 | } 114 | 115 | private fun launchLocationAutoCompleteActivity(requestCode: Int) { 116 | val fields: List = 117 | listOf(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG) 118 | val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields) 119 | .build(this) 120 | startActivityForResult(intent, requestCode) 121 | } 122 | 123 | override fun onMapReady(googleMap: GoogleMap) { 124 | this.googleMap = googleMap 125 | } 126 | 127 | override fun showNearbyCabs(latLngList: List) { 128 | nearbyCabMarkerList.clear() 129 | for (latLng in latLngList) { 130 | val nearbyCabMarker = addCarMarkerAndGet(latLng) 131 | nearbyCabMarkerList.add(nearbyCabMarker) 132 | } 133 | } 134 | 135 | override fun informCabBooked() { 136 | nearbyCabMarkerList.forEach { it.remove() } 137 | nearbyCabMarkerList.clear() 138 | requestCabButton.visibility = View.GONE 139 | statusTextView.text = getString(string.your_cab_is_booked) 140 | } 141 | 142 | override fun showPath(latLngList: List) { 143 | val builder = LatLngBounds.Builder() 144 | for (latLng in latLngList) { 145 | builder.include(latLng) 146 | } 147 | val bounds = builder.build() 148 | googleMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 2)) 149 | val polylineOptions = PolylineOptions() 150 | polylineOptions.color(Color.GRAY) 151 | polylineOptions.width(5f) 152 | polylineOptions.addAll(latLngList) 153 | greyPolyLine = googleMap.addPolyline(polylineOptions) 154 | 155 | val blackPolylineOptions = PolylineOptions() 156 | blackPolylineOptions.color(Color.GRAY) 157 | blackPolylineOptions.width(5f) 158 | blackPolylineOptions.addAll(latLngList) 159 | blackPolyLine = googleMap.addPolyline(blackPolylineOptions) 160 | 161 | 162 | originMarker = addOriginDestinationMarkerAndGet(latLngList[0]) 163 | originMarker?.setAnchor(0.5f, 0.5f) 164 | destinationMarker = addOriginDestinationMarkerAndGet(latLngList[latLngList.size - 1]) 165 | destinationMarker?.setAnchor(0.5f, 0.5f) 166 | 167 | val polylineAnimator = AnimationUtils.polyLineAnimator() 168 | polylineAnimator.addUpdateListener { valueAnimator -> 169 | val percentValue = (valueAnimator.animatedValue as Int) 170 | val index = (greyPolyLine?.points!!.size * (percentValue / 100.0f)).toInt() 171 | blackPolyLine?.points = greyPolyLine?.points!!.subList(0, index) 172 | } 173 | polylineAnimator.start() 174 | 175 | } 176 | 177 | override fun updateCabLocation(latLng: LatLng) { 178 | if (movingCabMarker == null) 179 | movingCabMarker = addCarMarkerAndGet(latLng) 180 | if (previousLatLngFromServer == null) { 181 | currentLatLngFromServer = latLng 182 | previousLatLngFromServer = currentLatLngFromServer 183 | movingCabMarker?.position = currentLatLngFromServer 184 | movingCabMarker?.setAnchor(0.5f, 0.5f) 185 | animateCamera(currentLatLngFromServer) 186 | } else { 187 | previousLatLngFromServer = currentLatLngFromServer 188 | currentLatLngFromServer = latLng 189 | val valueAnimator = AnimationUtils.cabAnimator() 190 | valueAnimator.addUpdateListener { 191 | if (currentLatLngFromServer != null && previousLatLngFromServer != null 192 | ) { 193 | val multiplier = it.animatedFraction 194 | val nextLocation = LatLng( 195 | multiplier * currentLatLngFromServer!!.latitude + (1 - multiplier) * previousLatLngFromServer!!.latitude, 196 | multiplier * currentLatLngFromServer!!.longitude + (1 - multiplier) * previousLatLngFromServer!!.longitude 197 | ) 198 | movingCabMarker?.position = nextLocation 199 | val rotation = MapUtils.getRotation(previousLatLngFromServer!!, currentLatLngFromServer!!) 200 | if (!rotation.isNaN()) { 201 | movingCabMarker?.rotation = rotation 202 | } 203 | movingCabMarker?.setAnchor(0.5f, 0.5f) 204 | animateCamera(nextLocation) 205 | } 206 | } 207 | valueAnimator.start() 208 | } 209 | } 210 | 211 | override fun informCabIsArriving() { 212 | statusTextView.text = getString(string.your_cab_is_arriving) 213 | } 214 | 215 | override fun informCabArrived() { 216 | statusTextView.text = getString(string.your_cab_has_arrived) 217 | greyPolyLine?.remove() 218 | blackPolyLine?.remove() 219 | originMarker?.remove() 220 | destinationMarker?.remove() 221 | } 222 | 223 | override fun informTripStart() { 224 | statusTextView.text = getString(string.you_are_on_a_trip) 225 | previousLatLngFromServer = null 226 | } 227 | 228 | override fun informTripEnd() { 229 | statusTextView.text = getString(string.trip_end) 230 | nextRideButton.visibility = View.VISIBLE 231 | greyPolyLine?.remove() 232 | blackPolyLine?.remove() 233 | originMarker?.remove() 234 | destinationMarker?.remove() 235 | } 236 | 237 | override fun showRoutesNotAvailableError() { 238 | val error = getString(R.string.route_not_available_choose_different_locations) 239 | Toast.makeText(this, error, Toast.LENGTH_LONG).show() 240 | reset() 241 | } 242 | 243 | override fun showDirectionApiFailedError(error: String) { 244 | Toast.makeText(this, error, Toast.LENGTH_LONG).show() 245 | reset() 246 | } 247 | 248 | private fun reset() { 249 | statusTextView.visibility = View.GONE 250 | nextRideButton.visibility = View.GONE 251 | nearbyCabMarkerList.forEach { it.remove() } 252 | nearbyCabMarkerList.clear() 253 | previousLatLngFromServer = null 254 | currentLatLngFromServer = null 255 | if (currentLatLng != null) { 256 | moveCamera(currentLatLng) 257 | animateCamera(currentLatLng) 258 | setCurrentLocationAsPickUp() 259 | presenter.requestNearbyCabs(currentLatLng!!) 260 | } else { 261 | pickUpTextView.text = "" 262 | } 263 | pickUpTextView.isEnabled = true 264 | dropTextView.isEnabled = true 265 | dropTextView.text = "" 266 | movingCabMarker?.remove() 267 | greyPolyLine?.remove() 268 | blackPolyLine?.remove() 269 | originMarker?.remove() 270 | destinationMarker?.remove() 271 | dropLatLng = null 272 | greyPolyLine = null 273 | blackPolyLine = null 274 | originMarker = null 275 | destinationMarker = null 276 | movingCabMarker = null 277 | } 278 | 279 | private fun addOriginDestinationMarkerAndGet(latLng: LatLng): Marker { 280 | return googleMap.addMarker( 281 | MarkerOptions().position(latLng).flat(true) 282 | .icon(BitmapDescriptorFactory.fromBitmap(MapUtils.getDestinationBitmap())) 283 | ) 284 | } 285 | 286 | private fun addCarMarkerAndGet(latLng: LatLng): Marker { 287 | return googleMap.addMarker( 288 | MarkerOptions().position(latLng).flat(true) 289 | .icon(BitmapDescriptorFactory.fromBitmap(MapUtils.getCarBitmap(this))) 290 | ) 291 | } 292 | 293 | private fun animateCamera(latLng: LatLng?) { 294 | googleMap.animateCamera( 295 | CameraUpdateFactory.newCameraPosition( 296 | CameraPosition.Builder().target( 297 | latLng 298 | ).zoom(15.5f).build() 299 | ) 300 | ) 301 | } 302 | 303 | private fun moveCamera(latLng: LatLng?) { 304 | googleMap.moveCamera(CameraUpdateFactory.newLatLng(latLng)) 305 | } 306 | 307 | override fun onRequestPermissionsResult( 308 | requestCode: Int, 309 | permissions: Array, 310 | grantResults: IntArray 311 | ) { 312 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 313 | when (requestCode) { 314 | LOCATION_PERMISSION_REQUEST_CODE -> { 315 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 316 | when { 317 | PermissionUtils.isLocationEnabled(this) -> { 318 | setUpLocationListener() 319 | } 320 | else -> { 321 | PermissionUtils.showGPSNotEnabledDialog(this) 322 | } 323 | } 324 | } else { 325 | Toast.makeText( 326 | this, 327 | getString(R.string.location_permission_not_granted), 328 | Toast.LENGTH_LONG 329 | ).show() 330 | } 331 | } 332 | } 333 | } 334 | 335 | private fun setUpLocationListener() { 336 | fusedLocationProviderClient = FusedLocationProviderClient(this) 337 | // for getting the current location update after every 2 seconds 338 | val locationRequest = LocationRequest().setInterval(2000).setFastestInterval(2000) 339 | .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) 340 | locationCallback = object : LocationCallback() { 341 | override fun onLocationResult(locationResult: LocationResult) { 342 | super.onLocationResult(locationResult) 343 | if (currentLatLng == null) { 344 | for (location in locationResult.locations) { 345 | if (currentLatLng == null) { 346 | currentLatLng = LatLng(location.latitude, location.longitude) 347 | setCurrentLocationAsPickUp() 348 | enableMyLocationOnMap() 349 | moveCamera(currentLatLng) 350 | animateCamera(currentLatLng) 351 | presenter.requestNearbyCabs(currentLatLng!!) 352 | } 353 | } 354 | } 355 | } 356 | } 357 | fusedLocationProviderClient?.requestLocationUpdates( 358 | locationRequest, 359 | locationCallback, 360 | Looper.myLooper() 361 | ) 362 | } 363 | 364 | private fun enableMyLocationOnMap() { 365 | googleMap.setPadding(0, ViewUtils.dpToPx(48f), 0, 0) 366 | googleMap.isMyLocationEnabled = true 367 | } 368 | 369 | private fun setCurrentLocationAsPickUp() { 370 | pickUpLatLng = currentLatLng 371 | pickUpTextView.text = getString(string.current_location) 372 | } 373 | 374 | override fun onStart() { 375 | super.onStart() 376 | super.onStart() 377 | if (currentLatLng == null) { 378 | when { 379 | PermissionUtils.isAccessFineLocationGranted(this) -> { 380 | when { 381 | PermissionUtils.isLocationEnabled(this) -> { 382 | setUpLocationListener() 383 | } 384 | else -> { 385 | PermissionUtils.showGPSNotEnabledDialog(this) 386 | } 387 | } 388 | } 389 | else -> { 390 | PermissionUtils.requestAccessFineLocationPermission( 391 | this, 392 | LOCATION_PERMISSION_REQUEST_CODE 393 | ) 394 | } 395 | } 396 | } 397 | } 398 | 399 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 400 | super.onActivityResult(requestCode, resultCode, data) 401 | if (requestCode == PICKUP_REQUEST_CODE || requestCode == DROP_REQUEST_CODE) { 402 | when (resultCode) { 403 | Activity.RESULT_OK -> { 404 | val place = Autocomplete.getPlaceFromIntent(data!!) 405 | Log.d(TAG, "Place: " + place.name + ", " + place.id + ", " + place.latLng) 406 | when (requestCode) { 407 | PICKUP_REQUEST_CODE -> { 408 | pickUpTextView.text = place.name 409 | pickUpLatLng = place.latLng 410 | checkAndShowRequestButton() 411 | } 412 | DROP_REQUEST_CODE -> { 413 | dropTextView.text = place.name 414 | dropLatLng = place.latLng 415 | checkAndShowRequestButton() 416 | } 417 | } 418 | } 419 | AutocompleteActivity.RESULT_ERROR -> { 420 | val status: Status = Autocomplete.getStatusFromIntent(data!!) 421 | Log.d(TAG, status.statusMessage!!) 422 | } 423 | Activity.RESULT_CANCELED -> { 424 | Log.d(TAG, "Place Selection Canceled") 425 | } 426 | } 427 | } 428 | } 429 | 430 | override fun onDestroy() { 431 | presenter.onDetach() 432 | fusedLocationProviderClient?.removeLocationUpdates(locationCallback) 433 | super.onDestroy() 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 17 | 24 | 380 | --------------------------------------------------------------------------------