diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a092402c --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +/.idea/* +.idea/gradle.xml +.idea/misc.xml diff --git a/README.md b/README.md index 877fb545..8528559d 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,42 @@ -# Code-with-Google-Maps-2023 - Hack2skill +#### Team Name - Girl Geeks +#### Problem Statement - Pregnancy Friendly Map - Navigating Motherhood with Ease +#### Team Leader Email - bhoomi.vaghasiya12@gmail.com -Welcome to the official repository for the Code-with-Google-Maps-2023 organized by Hack2skill! +![LOGO](/images/logo.png) -## Getting Started +### A Brief of the Prototype: +- This application is specially designed for Pregnant woman. She might use mapping services and applications for various purpose related to their health, safety, and comfort during pregnancy. So, we have planned to make a pregnancy friendly map that provides customised features and valuable information to support expectant mothers during significant phase of their lives. This map can consider mother's specific needs and provide her a safe and comfortable journey. +- This specialised Google Maps Application provides customised features like maternity facility Locator, emergency assistance, healthy eating options, mood swings changer by giving options for cravings and shopping. Also, expectant moms able to view live roads for safety and comfort. After analysing the road, simply navigates to the route using Google map navigation with ease. Whenever moms plans to go to any place that may risk their health, app will give them alert not to go for such places. +- This app can be owned by mother as well as any relative that concerns about the mother and her baby. We are helping expectant moms during their amazing and sensitive phase of life. -To get started with the Code-with-Google-Maps-2023 repository, follow these steps: +## UseCase Diagram: +The UseCase diagram represents the basic functionality of launching the application. The app prompts the user for necessary location permissions to provide accurate location-based services. The app offers users various map view options (e.g., satellite view, terrain view) for a customized mapping experience. It will allow users to explore different features available within the application. The map enables users to view live roads and surroundings using Street View functionality. Using this functionality, users can use navigational assistance, helping them find the best routes to their desired destinations. It will also allow users to search for specific locations such as hospitals, pharmacies, restaurants, and more. +![UML_DIAGRAM](/images/usecase_diagram.png) -### Submission Instruction: - 1. Fork this repository - 2. Create a folder with your Team Name - 3. Upload all the code and necessary files in the created folder - 4. Upload a **README.md** file in your folder with the below mentioned informations. - 5. Generate a Pull Request with your Team Name. (Example: submission-XYZ_team) +### Screenshots: +![App Screenshot](/images/screenshot1.png) +![App Screenshot](/images/screenshot2.png) -### README.md must consist of the following information: -#### Team Name - -#### Problem Statement - -#### Team Leader Email - +### Tech Stack: +- Language - Kotlin +- Google Maps SDK for android +- Google Maps APIS - StreetView API, Routes API, Places API, Elevation API +- IDE - Android Studio +- Http Library - Retrofit -### A Brief of the Prototype: - This section must include UML Diagrams and prototype description - -### Tech Stack: - List Down all technologies used to Build the prototype - ### Step-by-Step Code Execution Instructions: - This Section must contain a set of instructions required to clone and run the prototype so that it can be tested and deeply analyzed - +1. Clone the repository in Android Studio +2. replace with your API_KEY in local.properties +3. Enable StreetView API, Routes API, Places API, Elevation API in your project in google maps platform. +4. Compile and build the project +5. Connect a device or Emulator +6. Run the application + ### Future Scope: - Write about the scalability and futuristic aspects of the prototype developed +- Speed Limit Alerts: Provide speed limit information for specific routes to help pregnant women stay within safe speed limits while traveling. +- In-App Recommendations: Add navigation features within the app so users can easily find and follow routes to their destinations. +- Places Suggestions: Offer suggestions for places of interest along selected routes during navigation, making it convenient for users to find amenities and services. +- Emergency Help: Include emergency buttons or hotlines in the app for immediate access to local emergency services in case of urgent needs. +- Exclusive Discounts: Collaborate with local businesses to provide exclusive discounts and promotions to expectant mothers using the app, making it more valuable for them. +- MOM's Friendly Design: Enhance the app's user interface to make it more user-friendly for mothers. \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..f44b621e --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +/build +/local.properties diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..98956645 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,88 @@ +import org.jetbrains.kotlin.konan.properties.Properties +import com.android.build.api.variant.BuildConfigField + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") +} + +android { + namespace = "com.map.mom" + compileSdk = 34 + + defaultConfig { + applicationId = "com.map.mom" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + viewBinding = true + } +} + +androidComponents { + val localProperties = Properties() + localProperties.load(rootProject.file("local.properties").reader()) + + onVariants { + it.buildConfigFields.put( + "MAPS_API_KEY", + BuildConfigField( + "String", + "\"${localProperties.getProperty("MAPS_API_KEY")}\"", + "Google maps api key" + ) + ) + } +} + +configurations.all { + resolutionStrategy { + // Force the specific version of play-services-maps + force("com.google.android.gms:play-services-maps:18.2.0") + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.10.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation("com.intuit.sdp:sdp-android:1.1.0") + + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + + implementation("com.google.android.gms:play-services-location:21.0.1") + implementation("com.google.android.libraries.places:places:3.2.0") + implementation("com.google.maps.android:android-maps-utils:3.4.0") + implementation("com.google.android.gms:play-services-maps:18.2.0") + + +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/map/mom/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/map/mom/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..34613eea --- /dev/null +++ b/app/src/androidTest/java/com/map/mom/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.map.mom + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.map.mom", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..d0765a02 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 00000000..c95d9f3b Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/map/mom/adapters/FeaturesAdapter.kt b/app/src/main/java/com/map/mom/adapters/FeaturesAdapter.kt new file mode 100644 index 00000000..8e096627 --- /dev/null +++ b/app/src/main/java/com/map/mom/adapters/FeaturesAdapter.kt @@ -0,0 +1,102 @@ +package com.map.mom.adapters + +import android.content.Context +import android.graphics.drawable.GradientDrawable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.cardview.widget.CardView +import androidx.core.content.res.ResourcesCompat +import androidx.recyclerview.widget.RecyclerView +import com.map.mom.utility.Constants +import com.map.mom.models.Feature +import com.map.mom.R +import com.map.mom.databinding.ItemFeatureBinding + +class FeaturesAdapter( + private val mContext: Context, + private val features: List, + private val iOnFeatureItemClickListener: IOnFeatureItemClickListener +) : + RecyclerView.Adapter() { + + private lateinit var shapeDrawable: GradientDrawable + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val binding = ItemFeatureBinding.bind(itemView) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = ItemFeatureBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ViewHolder(binding.root) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val feature = features[position] + holder.binding.apply { + tvFeature.text = Constants.featureTextMap[feature.name] ?: "" + if (feature.isSelected) { + cardview.setFeatureSelected(feature.name) + } else { + cardview.setFeatureUnSelected() + } + ivFeature.setImageDrawable( + ResourcesCompat.getDrawable( + mContext.resources, + Constants.featureImageMap[feature.name] ?: R.drawable.activity, + null + ) + ) + } + holder.itemView.setOnClickListener { + iOnFeatureItemClickListener.onItemClick(feature, position) + } + } + + private fun initDrawable() { + shapeDrawable = GradientDrawable() + shapeDrawable.setColor( + ResourcesCompat.getColor( + mContext.resources, + R.color.background, + null + ) + ) + shapeDrawable.cornerRadius = 15.dpToPx().toFloat() + } + + private fun Int.dpToPx(): Int = (this * mContext.resources.displayMetrics.density).toInt() + + override fun getItemCount(): Int { + return features.size + } + + fun getSelectedFeature(): String?{ + val selectedFeature = features.find { it.isSelected } + return selectedFeature?.name + } + + private fun CardView.setFeatureSelected(featureName: String) { +//Apply same color to card as Map marker + //initialize for every different feature + initDrawable() + shapeDrawable.setStroke( + 5.dpToPx(), + ResourcesCompat.getColor( + mContext.resources, + Constants.featureColorMap[featureName] ?: R.color.grey, + null + ) + ) + background = shapeDrawable + setContentPadding(10, 10, 10, 10) + } + + private fun CardView.setFeatureUnSelected(){ + setContentPadding(0, 0, 0, 0) + } + + interface IOnFeatureItemClickListener { + fun onItemClick(feature: Feature, position: Int) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/adapters/RouteDetailAdapter.kt b/app/src/main/java/com/map/mom/adapters/RouteDetailAdapter.kt new file mode 100644 index 00000000..be53386f --- /dev/null +++ b/app/src/main/java/com/map/mom/adapters/RouteDetailAdapter.kt @@ -0,0 +1,87 @@ +package com.map.mom.adapters + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.map.mom.R +import com.map.mom.models.RouteDetailData +import com.map.mom.databinding.ItemRouteDetailsBinding + +class RouteDetailAdapter( + private val mContext: Context, + private val dataList: List, + private val iOnRouteDetailsItemClickListener: IOnRouteDetailsItemClickListener +) : + RecyclerView.Adapter() { + +// var shapeDrawable = ShapeDrawable() + private var lastSelectedItemPosition = 0 + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RouteDetailViewHolder { + return RouteDetailViewHolder( + ItemRouteDetailsBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ).root + ) + } + + override fun getItemCount(): Int { + return dataList.size + } + + override fun onBindViewHolder(holder: RouteDetailViewHolder, position: Int) { + holder.binding.apply { + with(dataList[position]) { + if (position == 0) { + tvRouteName.text = mContext.getString(R.string.txt_default_route) + } + else{ + tvRouteName.text = "Route ${position+1}" + } + tvDistance.text = distance + tvDuration.text = duration + if (isSelected) { + ivTick.visibility = View.VISIBLE + } else { + ivTick.visibility = View.GONE + } + holder.itemView.apply { + changeBackground(routeColor) + setOnClickListener{ + iOnRouteDetailsItemClickListener.onRouteDetailItemClick(position, lastSelectedItemPosition) + changeRouteSelection(position) + } + } + + } + } + } + + private fun changeRouteSelection(position: Int){ + lastSelectedItemPosition = position + } + + private fun View.changeBackground(routeColor: Int) { + Log.d("Adapter", "route color = $routeColor") + val gradientDrawable = GradientDrawable() + gradientDrawable.shape = GradientDrawable.RECTANGLE + gradientDrawable.cornerRadius = 12f + gradientDrawable.setStroke(12, routeColor) + gradientDrawable.setColor(Color.WHITE) + this.background = gradientDrawable + } + + class RouteDetailViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val binding = ItemRouteDetailsBinding.bind(itemView) + } + + interface IOnRouteDetailsItemClickListener{ + fun onRouteDetailItemClick(position: Int, lastSelectedItemPostion: Int) + } +} diff --git a/app/src/main/java/com/map/mom/models/Feature.kt b/app/src/main/java/com/map/mom/models/Feature.kt new file mode 100644 index 00000000..aadd89e3 --- /dev/null +++ b/app/src/main/java/com/map/mom/models/Feature.kt @@ -0,0 +1,7 @@ +package com.map.mom.models + +data class Feature ( + var name:String, + var image:Int, + var isSelected:Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/models/RouteDetailData.kt b/app/src/main/java/com/map/mom/models/RouteDetailData.kt new file mode 100644 index 00000000..1c38c09b --- /dev/null +++ b/app/src/main/java/com/map/mom/models/RouteDetailData.kt @@ -0,0 +1,9 @@ +package com.map.mom.models + +data class RouteDetailData( + var distance: String? = null, + var distanceInMtrs: Int, + var duration: String? = null, + var isSelected: Boolean = false, + val routeColor: Int +) diff --git a/app/src/main/java/com/map/mom/retrofit/RetrofitAPI.kt b/app/src/main/java/com/map/mom/retrofit/RetrofitAPI.kt new file mode 100644 index 00000000..0f968256 --- /dev/null +++ b/app/src/main/java/com/map/mom/retrofit/RetrofitAPI.kt @@ -0,0 +1,34 @@ +package com.map.mom.retrofit + +import okhttp3.RequestBody +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Query + +interface RetrofitAPI { + + @GET("maps/api/place/nearbysearch/json") + fun getNearbyPlaces( + @Query("location") location: String, + @Query("radius") radius: Int, + @Query("type") type: String, + @Query("keyword") keyword: String, + @Query("key") apiKey: String + ): Call + + @POST("directions/v2:computeRoutes") + fun findRoute( + @Query("key") apiKey: String, + @Query("fields") fields: String, + @Body requestBody: RequestBody + ): Call + + @GET("maps/api/elevation/json") + fun getElevationData( + @Query("locations") locations: String, + @Query("key") apiKey: String + ): Call +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/retrofit/RetrofitHelper.kt b/app/src/main/java/com/map/mom/retrofit/RetrofitHelper.kt new file mode 100644 index 00000000..4679db1e --- /dev/null +++ b/app/src/main/java/com/map/mom/retrofit/RetrofitHelper.kt @@ -0,0 +1,25 @@ +package com.map.mom.retrofit + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object RetrofitHelper { + fun getRetrofitObject(): RetrofitAPI { + val retrofit = Retrofit.Builder() + .baseUrl("https://maps.googleapis.com/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + + return retrofit.create(RetrofitAPI::class.java) + } + + fun getRetrofitRoutesObject(): RetrofitAPI { + val retrofit = Retrofit.Builder() + .baseUrl("https://routes.googleapis.com/") + .addConverterFactory(GsonConverterFactory.create()) + .build() + + return retrofit.create(RetrofitAPI::class.java) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/retrofit/RetrofitResponseManager.kt b/app/src/main/java/com/map/mom/retrofit/RetrofitResponseManager.kt new file mode 100644 index 00000000..62f614e1 --- /dev/null +++ b/app/src/main/java/com/map/mom/retrofit/RetrofitResponseManager.kt @@ -0,0 +1,29 @@ +package com.map.mom.retrofit + +import android.util.Log +import okhttp3.ResponseBody +import org.json.JSONObject +import retrofit2.Response + +object RetrofitResponseManager { + + fun onResponseFailed(response: Response, callerTag: String) { + try { + val errorBodyString = response.errorBody()?.string() + errorBodyString?.let { + val errorJSON = JSONObject(errorBodyString) + Log.d( + callerTag, + "response is failed: ${response.code()}\n $errorBodyString" + ) + val errorMessage = + errorJSON.getString("error") // Replace "error" with the actual key in the error response + Log.d(callerTag, "Error message: $errorMessage") + } + } catch (e: Exception) { + // Handle JSON parsing error or other exceptions + Log.e(callerTag, "Error parsing error body: ${e.message}") + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/screens/MainActivity.kt b/app/src/main/java/com/map/mom/screens/MainActivity.kt new file mode 100644 index 00000000..b36e632a --- /dev/null +++ b/app/src/main/java/com/map/mom/screens/MainActivity.kt @@ -0,0 +1,873 @@ +package com.map.mom.screens + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Color +import android.location.Location +import android.location.LocationManager +import android.net.Uri +import android.os.Bundle +import android.os.Looper +import android.text.Editable +import android.text.TextWatcher +import android.util.Log +import android.view.Gravity +import android.view.View +import android.widget.EditText +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.lifecycle.ViewModelProvider +import com.google.android.gms.common.api.Status +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.Priority +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import com.google.android.libraries.places.api.Places +import com.google.android.libraries.places.api.model.Place +import com.google.android.libraries.places.widget.AutocompleteSupportFragment +import com.google.android.libraries.places.widget.listener.PlaceSelectionListener +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.gson.Gson +import com.google.maps.android.PolyUtil +import com.map.mom.BuildConfig +import com.map.mom.R +import com.map.mom.adapters.FeaturesAdapter +import com.map.mom.adapters.RouteDetailAdapter +import com.map.mom.databinding.ActivityMainBinding +import com.map.mom.models.Feature +import com.map.mom.models.RouteDetailData +import com.map.mom.retrofit.RetrofitHelper +import com.map.mom.retrofit.RetrofitResponseManager +import com.map.mom.utility.Constants +import com.map.mom.utility.Constants.GOOGLE_MAPS_PACKAGE_NAME +import com.map.mom.utility.Constants.MAP_TYPE_HYBRID +import com.map.mom.utility.Constants.MAP_TYPE_NORMAL +import com.map.mom.utility.Constants.MAP_TYPE_TERRAIN +import com.map.mom.utility.Constants.iconMarker +import com.map.mom.utility.Constants.placeAPIFields +import com.map.mom.utility.Constants.routeColorList +import com.map.mom.utility.FeatureUtility +import com.map.mom.utility.FeatureUtility.getKeywordForFeatureType +import com.map.mom.utility.FeatureUtility.removeAllFeatureMarkers +import com.map.mom.utility.changeMapView +import com.map.mom.utility.showToast +import com.map.mom.viewmodels.FeaturesViewModel +import okhttp3.MediaType +import okhttp3.RequestBody +import okhttp3.ResponseBody +import org.json.JSONArray +import org.json.JSONObject +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.util.Locale + +class MainActivity : AppCompatActivity(), OnMapReadyCallback, + FeaturesAdapter.IOnFeatureItemClickListener, + RouteDetailAdapter.IOnRouteDetailsItemClickListener { + + companion object { + private val TAG = this::class.java.name + } + + private lateinit var mMap: GoogleMap + private lateinit var binding: ActivityMainBinding + private var autocompleteFragment: AutocompleteSupportFragment? = null + + private lateinit var featuresAdapter: FeaturesAdapter + private lateinit var routeDetailAdapter: RouteDetailAdapter + private lateinit var featuresViewModel: FeaturesViewModel + + private lateinit var fusedLocationClient: FusedLocationProviderClient + private var locationCallback: LocationCallback? = null + + private var currentLatLng: LatLng? = null + private var marker: Marker? = null + private var markerMap: MutableMap> = mutableMapOf() + private var routeDetailDataList: MutableList = mutableListOf() + private var polylineMap: MutableMap = mutableMapOf() + private var mapTypeList = listOf(MAP_TYPE_NORMAL, MAP_TYPE_HYBRID, MAP_TYPE_TERRAIN) + private var mapTypeCurrentIndex = 0 + private var destinationLatLang: LatLng? = null + private var destinationMarker: Marker? = null + private var destinationLabelMarker: Marker? = null + private var routeLabelMarkers: MutableList = mutableListOf() + + override fun onStart() { + super.onStart() + setupLocationPermission() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + featuresViewModel = ViewModelProvider(this)[FeaturesViewModel::class.java] + // Obtain the SupportMapFragment and get notified when the map is ready to be used. + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + mapFragment.getMapAsync(this) + } + + //****************************** START GOOGLE MAP VIEW *********************************/ + /* + * Calls On Google map ready + * Usage: Setting location permissions and map features are ready to use + * */ + override fun onMapReady(googleMap: GoogleMap) { + mMap = googleMap + mMap.isBuildingsEnabled = true + + setupLocationPermission() + setupFeaturesAdapter() + setupPlacesAPI() + + /* Show Street view whenever click event occurs on Google Map*/ + mMap.setOnMapClickListener { clickedLatLng -> + enableStreetView(clickedLatLng) + } + + /* Show different types of Map Views */ + binding.imgBtnChangeMapView.setOnClickListener { + mapTypeCurrentIndex = (mapTypeCurrentIndex + 1) % mapTypeList.size + mMap.changeMapView(mapTypeList[mapTypeCurrentIndex]) + } + + /* Start Navigating to selected route */ + binding.imgBtnStartNavigation.setOnClickListener { + if (destinationLatLang != null) { + startNavigationActivity() + } else { + showToast(getString(R.string.empty_destination_navigation_msg)) + } + } + } + + //****************************** END GOOGLE MAP VIEW *********************************/ + + //****************************** START STREET VIEW API USAGE *********************************/ + + /* + * Start showing street view for particular location + * */ + private fun enableStreetView(latLng: LatLng) { + val intent = Intent(this, StreetViewActivity::class.java) + intent.putExtra("lat", latLng.latitude) + intent.putExtra("lng", latLng.longitude) + startActivity(intent) + } + + //****************************** END STREET VIEW API USAGE *********************************/ + + //****************************** START PLACES API INITIALIZATION *********************************/ + + /* + * This function sets up the places api and it's relative autocomplete search view + * Usage: To search places worldwide + * */ + private fun setupPlacesAPI() { + if (!Places.isInitialized()) { //Initialize Google Place API + Places.initialize(applicationContext, BuildConfig.MAPS_API_KEY, Locale.US) + } + Places.createClient(this) + + //Initialize the AutocompleteSupportFragment to search for a Particular place + autocompleteFragment = + supportFragmentManager.findFragmentById(R.id.autocomplete_fragment) as AutocompleteSupportFragment + if (autocompleteFragment != null) { + // Specify the types of place data to return. + autocompleteFragment!!.setPlaceFields(placeAPIFields) + // Set up a PlaceSelectionListener to handle the response. + autocompleteFragment!!.setOnPlaceSelectedListener(object : PlaceSelectionListener { + override fun onPlaceSelected(place: Place) { + setDestinationMarker(place) + Log.d(TAG, "Destination place id = ${place.id}") + if (place.latLng != null) { + removeAllFeatureMarkers( + markerMap, + featuresAdapter.getSelectedFeature() ?: Constants.FEATURE_ACTIVE + ) + getElevationData(place.latLng!!, place.name!!) + findRoute(place) + } + } + + override fun onError(status: Status) { + Log.i(TAG, "An error occurred: $status") + } + }) + //handle on search cancel + val searchView = autocompleteFragment!!.requireView() + .findViewById(com.google.android.libraries.places.R.id.places_autocomplete_search_input) + searchView.hint = getString(R.string.search_view_hint) + searchView.textSize = 16F + searchView.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int + ) { + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable?) { + // Check if the text becomes empty, indicating that the user clicked the cancel button + if (s.isNullOrEmpty()) { + // Handle the cancel button click here + emptyRoutes() + } + } + }) + } + } + + //****************************** END PLACES API INITIALIZATION *********************************/ + + //****************************** START ROUTES API USAGE *********************************/ + + /* + * This function calls when user search for a destination and tries to find route to reach + * Usage: Get routes using Google's Routes API + * */ + private fun findRoute(place: Place) { + val fields = + "routes.localizedValues.distance,routes.localizedValues.duration,routes.polyline,routes.distanceMeters" + RetrofitHelper.getRetrofitRoutesObject().findRoute( + apiKey = BuildConfig.MAPS_API_KEY, + fields = fields, + requestBody = RequestBody.create( + MediaType.parse("application/json"), + getRoutesRequest(place.id).toString() + ) + ).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + val responseJSON = response.body()?.string() + responseJSON?.let { + val jsonRouteObject = JSONObject(responseJSON) + manageRouteData(jsonRouteObject) + } + } else { + RetrofitResponseManager.onResponseFailed(response, TAG) + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d(TAG, "Response failed") + Log.getStackTraceString(t) + hideProgressbar() + } + }) + } + + /* + * This function prepares request data required in Google's Routes API + * */ + private fun getRoutesRequest(destinationPlaceId: String?): JSONObject { + val jsonParams = mutableMapOf( + "origin" to mutableMapOf( + "location" to mutableMapOf( + "latLng" to mutableMapOf( + "latitude" to currentLatLng!!.latitude, + "longitude" to currentLatLng!!.longitude + ) + ) + ), + "destination" to mutableMapOf("placeId" to destinationPlaceId), + "travelMode" to "DRIVE", + "computeAlternativeRoutes" to "true", + "routingPreference" to "TRAFFIC_AWARE", + "languageCode" to "en-US", + ) + return JSONObject(Gson().toJson(jsonParams)) + } + + /* + * Manage Routes data received from Google's Routes API + * Usage: Extract requested data and show routes and markers on map + * */ + private fun manageRouteData(jsonRouteObject: JSONObject) { + val routesArr = jsonRouteObject.optJSONArray("routes") + Log.d(TAG, "Response Json = $routesArr") + if (routesArr != null && routesArr.length() > 0) { + var totalRoutesAvailable = routesArr.length() + for (i in 0 until routesArr.length()) { + val routeObj = JSONObject(routesArr[i].toString()) + val distanceInMtrs = routeObj.optInt("distanceMeters") + val polylineJsonObj = routeObj.optJSONObject("polyline") + val encodedPolyline = polylineJsonObj?.optString("encodedPolyline") + val localizedValuesJsonObj = routeObj.optJSONObject("localizedValues") + val distanceObj = localizedValuesJsonObj?.optJSONObject("distance") + val durationObj = localizedValuesJsonObj?.optJSONObject("duration") + val distance = distanceObj?.optString("text") + val duration = durationObj?.optString("text") + Log.d( + TAG, + "Got route data: distance = $distance\nduration = $duration\nencodedPolyline = $encodedPolyline" + ) + routeDetailDataList.add( + RouteDetailData( + distance, + distanceInMtrs, + duration, + i == 0, + routeColorList[i] + ) + ) + val index = --totalRoutesAvailable + polylineMap[i] = + createRoute(encodedPolyline, routeColorList[i], index) + routeLabelMarkers.add(showRouteLabel(polylineMap[i]!!, duration!!)) + } + setUpRouteAdapter(routeDetailDataList) + } else { + showToast(getString(R.string.no_route_found)) + } + } + + /* + * This function draw route as polyline on map + * */ + private fun createRoute(encodedPolyline: String?, routeColor: Int, routeNo: Int): Polyline { + val latLngList = PolyUtil.decode(encodedPolyline) + val polyline = mMap.addPolyline( + PolylineOptions().addAll(latLngList).color(routeColor).zIndex(routeNo.toFloat()) + .width(20.0F) + ) + setMapViewCamera(latLngList) + return polyline + } + + /* + * This function will be called whenever search places changes/cancelled + * Usage: removes all drawn paths on Google map, + * removes route details show as cards, + * destination marker + * and a route labels + * */ + private fun emptyRoutes() { + val indicesToRemove = mutableListOf() + polylineMap.forEach { (index, polyline) -> + polyline.remove() + indicesToRemove.add(index) + } + + indicesToRemove.sortedDescending().forEach { index -> + polylineMap.remove(index) + if (index >= 0 && index < routeDetailDataList.size) { + routeDetailDataList.removeAt(index) + routeDetailAdapter.notifyItemRemoved(index) + } + } + routeLabelMarkers.forEach { it?.remove() } + destinationMarker?.remove() + destinationLabelMarker?.remove() + destinationLatLang = null + + //After removing All details of last search, Locate map to user's location + moveCameraToCurrentLocation() + } + + //****************************** END ROUTES API USAGE *********************************/ + + //****************************** START PLACES API USAGE *********************************/ + + /* + * Calls when any feature is selected + * Usage: To find near by places to user's live location based on selected feature + * */ + private fun getNearByPlaces(featureName: String, position: Int) { + if (featuresViewModel.featureList[position].isSelected) { + showProgressbar() + if (ActivityCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + ) { + Log.d(TAG, "Missing permissions for locating places") + requestPermissions(Constants.LOCATION_PERMISSIONS, 111) + return + } else { + fusedLocationClient.lastLocation.addOnSuccessListener { location -> + if (location != null) { + // Get the latitude and longitude of the user's current location. + FeatureUtility.getFeatureTypeFromName(featureName) + .forEach { featureType -> + val call = RetrofitHelper.getRetrofitObject().getNearbyPlaces( + location = "${location.latitude},${location.longitude}", + radius = 3000, // Radius in meters. + type = featureType, + keyword = getKeywordForFeatureType(featureName), + apiKey = BuildConfig.MAPS_API_KEY + ) + call.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + val placesJson = response.body()?.string() + placesJson?.let { + val jsonObject = JSONObject(placesJson) + if (jsonObject.getString("status") == "OK") { + val resultsArray = + jsonObject.getJSONArray("results") + locateFeatureMarkers(resultsArray, featureName) + } else { + // Handle the case where the response status is not OK + showToast("Place data not found") + } + } + } else { + // Handle the case where the response status is not OK + showToast("Failed to find place data") + RetrofitResponseManager.onResponseFailed(response, TAG) + } + } + + override fun onFailure( + call: Call, + t: Throwable + ) { + Log.getStackTraceString(t) + } + }) + } + // Get the nearby hospitals using the Nearby Places API. + } + } + } + } else { + removeAllFeatureMarkers(markerMap, featureName) + } + } + + /* + * Calls when selected feature data successfully received from Google Places API + * Usage: Based on selected feature, show all places as marker on google map + * */ + fun locateFeatureMarkers(places: JSONArray, featureName: String) { + val latLngList = mutableListOf() + for (i in 0 until places.length()) { + val place = places.getJSONObject(i) + val name = place.getString("name") + val vicinity = place.getString("vicinity") + val geometry = place.getJSONObject("geometry") + val location = geometry.getJSONObject("location") + val lat = location.getDouble("lat") + val lng = location.getDouble("lng") + val latLng = LatLng(lat, lng) + latLngList.add(latLng) + val marker = showMarker(name, latLng, vicinity, iconMarker[featureName]) + markerMap[featureName]!!.add(marker!!) + } + if (latLngList.isNotEmpty()) { + setMapViewCamera(latLngList) + } + hideProgressbar() + } + + //****************************** END PLACES API USAGE *********************************/ + + //****************************** START ELEVATION API USAGE *********************************/ + + /* + * This function helps to calculate elevation for particular place + * Usage: Get Elevation of place from Google's Elevation API + * */ + private fun getElevationData(destinationLatLang: LatLng, destinationName: String) { + Log.d( + TAG, + "Get elevation for : (${destinationLatLang.latitude}, ${destinationLatLang.longitude})" + ) + RetrofitHelper.getRetrofitObject().getElevationData( + "${destinationLatLang.latitude},${destinationLatLang.longitude}", + apiKey = BuildConfig.MAPS_API_KEY + ).enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + val responseJson = response.body()?.string() + if (responseJson != null) { + val jsonElevationDataObj = JSONObject(responseJson) + val status = jsonElevationDataObj.optString("status") + if (status.equals("OK")) { + val resultArr = jsonElevationDataObj.optJSONArray("results") + if (resultArr != null) { + for (i in 0 until resultArr.length()) { + val dataObj = JSONObject(resultArr[i].toString()) + val elevation = dataObj.optString("elevation") + manageElevationData(elevation, destinationName) + } + } + } else { + RetrofitResponseManager.onResponseFailed(response, TAG) + } + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d(TAG, "Response failed") + Log.getStackTraceString(t) + hideProgressbar() + } + }) + } + + /* + * Handle elevation data received for Google Elevation API + * Usage: Show alerts on risky places for mother and baby + * */ + private fun manageElevationData(elevation: String, destinationName: String) { + if (elevation.toDouble() > Constants.max_safe_elevation_mtr) { + MaterialAlertDialogBuilder(this) + .setTitle("Avoid Risk: High Altitude Area - $destinationName") + .setMessage( + "This region may have lower oxygen pressure " + + "which can affect the oxygen level of both " + + "the mother and a developing baby" + ) + .show() + } + } + + //****************************** END ELEVATION API USAGE *********************************/ + + /* + * Check for provider to get location data + * */ + private fun isProviderEnabled(): Boolean { + val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager + return (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled( + LocationManager.NETWORK_PROVIDER + )) + } + + /* + * To request live location up to date + * */ + private fun requestLocationUpdates() { + val locationRequest = LocationRequest + .Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000).build() + + if (locationCallback == null) { + locationCallback = object : LocationCallback() { + override fun onLocationResult(locationResult: LocationResult) { + Log.d(TAG, "etDeviceCurrentLocation: using request location updates") + for (location in locationResult.locations) { + if (location != null && currentLatLng == null) { + currentLatLng = LatLng(location.latitude, location.longitude) + Log.d(TAG, "$currentLatLng") + } + } + } + } + if (ActivityCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + ) { + setupLocationPermission() + } + fusedLocationClient.requestLocationUpdates( + locationRequest, + locationCallback!!, + Looper.getMainLooper() + ) + } + } + + /* + * To stop live location updates + * */ + private fun stopLocationUpdates() { + if (locationCallback != null) fusedLocationClient.removeLocationUpdates(locationCallback!!) + } + + /* + * Check for location permission granted or not + * */ + private fun isLocationPermissionGranted(): Boolean { + return !(ContextCompat.checkSelfPermission( + applicationContext, + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission( + applicationContext, + Manifest.permission.ACCESS_COARSE_LOCATION + ) != PackageManager.PERMISSION_GRANTED) + } + + /* + * This function set up the permission for location + * */ + private fun setupLocationPermission() { + if (!isLocationPermissionGranted()) { + requestPermissions(Constants.LOCATION_PERMISSIONS, 111) + } else { + getCurrentLocation() + } + } + + /* + * TO get current(live) location of user + * */ + private fun getCurrentLocation() { + fusedLocationClient = LocationServices.getFusedLocationProviderClient(this) + try { + if (isProviderEnabled()) { + fusedLocationClient.lastLocation.addOnSuccessListener { location: Location? -> + if (location != null) { + currentLatLng = LatLng(location.latitude, location.longitude) + stopLocationUpdates() + showCurrentLocation() + } else { + Log.d(TAG, "Location Failed") + requestLocationUpdates() + } + // Got last known location. In some rare situations this can be null. + } + if (currentLatLng == null) { + requestLocationUpdates() + } + } + } catch (e: SecurityException) { + Log.d(TAG, e.message.toString()) + } + } + + /* + * To show live location on map as custom marker + * */ + private fun showCurrentLocation() { + if (currentLatLng != null && marker == null) { + val height = 120 + val width = 120 + val b = BitmapFactory.decodeResource(resources, R.drawable.marker_current) + val smallMarker = Bitmap.createScaledBitmap(b, width, height, false) + val smallMarkerIcon = BitmapDescriptorFactory.fromBitmap(smallMarker) + marker = mMap.addMarker( + MarkerOptions().position(currentLatLng!!).title("My Location").icon(smallMarkerIcon) + ) + moveCameraToCurrentLocation() + } + } + + /* + * show map view of user's live location + * */ + private fun moveCameraToCurrentLocation() { + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng!!, 13f)) + } + + /* + * To show location as marker on map + * */ + private fun showMarker( + name: String?, + latLng: LatLng?, + address: String?, + marker: Int? + ): Marker? { + Log.d(TAG, "show marker at: $address") + val height = 100 + val width = 100 + val b = BitmapFactory.decodeResource(resources, marker!!) + val smallMarker = Bitmap.createScaledBitmap(b, width, height, false) + val smallMarkerIcon = BitmapDescriptorFactory.fromBitmap(smallMarker) + return mMap.addMarker( + MarkerOptions().position( + latLng ?: currentLatLng + ?: LatLng(0.0, 0.0) + ).title("$name").icon(smallMarkerIcon) + ) + } + + /* + * To show map including all the locations requested + * */ + private fun setMapViewCamera(markerPositions: MutableList) { + val builder = LatLngBounds.Builder() + for (pos in markerPositions) { + builder.include(pos) + } + val bounds = builder.build() + val padding = 100 + mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, padding)) + } + + /* + * Calls when search for a particular place + * Usage: To show marker of searched location as destination + * */ + private fun setDestinationMarker(place: Place) { + destinationLatLang = place.latLng + destinationMarker = mMap.addMarker( + MarkerOptions() + .position(place.latLng ?: currentLatLng ?: LatLng(0.0, 0.0)) + .title(place.name) + .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)) + ) + } + + /* + * Show label in middle of drawn route on map + * */ + private fun showRouteLabel(polyline: Polyline, duration: String): Marker? { + // Create a custom TextView as the label + val labelView = TextView(this) + labelView.text = duration + labelView.setBackgroundColor(Color.BLACK) + labelView.setTextColor(Color.WHITE) + labelView.gravity = Gravity.CENTER + labelView.setPadding(16, 8, 16, 8) + + // Create a MarkerOptions object and set its position to the midpoint of the polyline. + val markerOptions = MarkerOptions().position(midPointOfPolyline(polyline)) + .icon(BitmapDescriptorFactory.fromBitmap(createLabelBitmap(labelView))) // Create a BitmapDescriptor from the TextView + + // Add the marker as label to the map. + destinationLabelMarker = mMap.addMarker(markerOptions)!! + destinationLabelMarker?.showInfoWindow() + return destinationLabelMarker + } + + /* + * This function creates label for route + * */ + private fun createLabelBitmap(view: View): Bitmap { + view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) + val bitmap = + Bitmap.createBitmap(view.measuredWidth, view.measuredHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + view.draw(canvas) + return bitmap + } + + /* + * This function calculates the midpoint of a polyline + * */ + private fun midPointOfPolyline(polyline: Polyline): LatLng { + val points = polyline.points + val size = points.size + Log.d(TAG, "points = $points\nsize = $size") + return if (size % 2 != 0) { + val mid = size / 2 + Log.d(TAG, "points = $points\nsize = $size\nmid=$mid") + LatLng( + (points[mid].latitude + points[mid - 1].latitude) / 2, + (points[mid].longitude + points[mid - 1].longitude) / 2 + ) + } else { + points[size / 2] + } + } + + /* + * Calls when any of the route is selected from list of route details + * Usage: To show polyline of route based on route data selection + * */ + private fun onRouteSelectionChange(clickedIndex: Int) { + //find highest index in map + val highestIndex = polylineMap.keys.maxOrNull() ?: 0 + //update indices in map + polylineMap.forEach { (index, polyline) -> + polyline.zIndex = + if (index == clickedIndex) highestIndex.toFloat() + else if (index >= highestIndex) (index - 1).toFloat() + else index.toFloat() + } + } + + /* + * Calls When Navigation is about to start + * Usage: Check and start Google map Navigation based on selected route location + * */ + private fun startNavigationActivity() { + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("google.navigation:q=${destinationLatLang?.latitude},${destinationLatLang?.longitude}") + ).apply { + `package` = GOOGLE_MAPS_PACKAGE_NAME + } + if (intent.resolveActivity(packageManager) != null) + startActivity(intent) + else + showToast(getString(R.string.google_maps_install_instruction)) + } + + /* + * To show all features at bottom of the map + * */ + private fun setupFeaturesAdapter() { + val list = featuresViewModel.featureList + for (feature in list) { //create marker list for each feature to handle check uncheck of feature on map + markerMap[feature.name] = mutableListOf() + } + featuresAdapter = FeaturesAdapter(this, list, this) + binding.rvFeatures.adapter = featuresAdapter + } + + /* + * To show each route details received from Google's Route API + * */ + private fun setUpRouteAdapter(routeDetailDataList: MutableList) { + routeDetailAdapter = RouteDetailAdapter(this, routeDetailDataList, this) + binding.rvRouteDetails.adapter = routeDetailAdapter + } + + private fun showProgressbar() { + binding.layoutProgressbar.progressbar.visibility = View.VISIBLE + } + + private fun hideProgressbar() { + binding.layoutProgressbar.progressbar.visibility = View.GONE + } + + /* + * Calls when any route detail data selected + * Usage: On route selection, manage route/polylines of the map, also adjust camera view of map + * */ + override fun onRouteDetailItemClick(position: Int, lastSelectedItemPostion: Int) { + routeDetailDataList[position].isSelected = true + routeDetailDataList[lastSelectedItemPostion].isSelected = false + routeDetailAdapter.notifyItemRangeChanged(0, routeDetailDataList.size) + onRouteSelectionChange(position) + setMapViewCamera(mutableListOf(currentLatLng!!, destinationLatLang!!)) + } + + /* + * Calls when any feature is selected + * Usage: On feature selection/de-selection, manage markers on the map + * */ + override fun onItemClick(feature: Feature, position: Int) { + featuresViewModel.onFeatureClick(position) + featuresAdapter.notifyItemChanged(position) + getNearByPlaces(feature.name, position) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/screens/SplashActivity.kt b/app/src/main/java/com/map/mom/screens/SplashActivity.kt new file mode 100644 index 00000000..c9a86ffb --- /dev/null +++ b/app/src/main/java/com/map/mom/screens/SplashActivity.kt @@ -0,0 +1,36 @@ +package com.map.mom.screens + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import com.map.mom.databinding.ActivitySplashBinding +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class SplashActivity : AppCompatActivity() { + + private val activityScope = CoroutineScope(Dispatchers.Main) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val binding = ActivitySplashBinding.inflate(layoutInflater) + setContentView(binding.root) + + activityScope.launch { + delay(2000) + startMainActivity() + } + } + + private fun startMainActivity() { + startActivity(Intent(this, MainActivity::class.java)) + finish() + } + + override fun onPause() { + activityScope.cancel() + super.onPause() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/screens/StreetViewActivity.kt b/app/src/main/java/com/map/mom/screens/StreetViewActivity.kt new file mode 100644 index 00000000..a4a2e477 --- /dev/null +++ b/app/src/main/java/com/map/mom/screens/StreetViewActivity.kt @@ -0,0 +1,97 @@ +package com.map.mom.screens + +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import com.google.android.gms.common.api.Status +import com.google.android.gms.maps.StreetViewPanorama +import com.google.android.gms.maps.SupportStreetViewPanoramaFragment +import com.google.android.gms.maps.model.LatLng +import com.google.android.libraries.places.api.Places +import com.google.android.libraries.places.api.model.Place +import com.google.android.libraries.places.widget.AutocompleteSupportFragment +import com.google.android.libraries.places.widget.listener.PlaceSelectionListener +import com.map.mom.BuildConfig +import com.map.mom.R +import com.map.mom.databinding.ActivityStreetViewBinding +import com.map.mom.utility.Constants.placeAPIFields +import com.map.mom.utility.showToast +import java.util.Locale + +class StreetViewActivity : AppCompatActivity() { + + companion object { + private val TAG = this::class.java.name + } + private lateinit var binding: ActivityStreetViewBinding + private var mStreetViewPanorama: StreetViewPanorama? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityStreetViewBinding.inflate(layoutInflater) + setContentView(binding.root) + + val streetViewPanoramaFragment = + supportFragmentManager + .findFragmentById(R.id.street_view_panorama) as SupportStreetViewPanoramaFragment + streetViewPanoramaFragment.initStreetViewFragment(savedInstanceState) + + val autocompleteFragment = + supportFragmentManager.findFragmentById(R.id.autocomplete_fragment) as AutocompleteSupportFragment + setUpPlaceAPI() + autocompleteFragment.placeSelectionHandler() + } + + private fun SupportStreetViewPanoramaFragment.initStreetViewFragment(savedInstanceState: Bundle?) { + //must be called on a main thread + this.getStreetViewPanoramaAsync { panorama -> + mStreetViewPanorama = panorama + if (savedInstanceState == null) { //no panoramas have been loaded + intent.extras?.let { + val lat = it.getDouble("lat") + val lng = it.getDouble("lng") + Log.d("Activity", "Streetview: latlang = ${LatLng(lat, lng)}") + showPanorama(LatLng(lat, lng)) + } + } + } + } + + private fun setUpPlaceAPI() { + if (!Places.isInitialized()) { + Places.initialize(applicationContext, BuildConfig.MAPS_API_KEY, Locale.US) + } + Places.createClient(this) + } + + private fun AutocompleteSupportFragment.placeSelectionHandler() { + this.setPlaceFields(placeAPIFields) + this.setOnPlaceSelectedListener(object : PlaceSelectionListener { + override fun onPlaceSelected(place: Place) { + if (place.latLng != null) { + showPanorama(place.latLng!!) + } + else{ + showToast("Place Not Found") + } + } + + override fun onError(status: Status) { + Log.i(TAG, "An error occurred: $status") + } + }) + } + + private fun showPanorama(latLng: LatLng) { + Log.d(TAG, "show street view for location: latLng = $latLng") + if (mStreetViewPanorama != null) { + try { + mStreetViewPanorama!!.setPosition(latLng) + } catch (e: Exception) { + showToast("No View Available") + Log.d(TAG, e.message.toString()) + e.printStackTrace() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/utility/Constants.kt b/app/src/main/java/com/map/mom/utility/Constants.kt new file mode 100644 index 00000000..0946484e --- /dev/null +++ b/app/src/main/java/com/map/mom/utility/Constants.kt @@ -0,0 +1,77 @@ +package com.map.mom.utility + +import android.Manifest +import android.graphics.Color +import com.google.android.libraries.places.api.model.Place +import com.map.mom.R + +object Constants { + + const val GOOGLE_MAPS_PACKAGE_NAME = "com.google.android.apps.maps" + + const val MAP_TYPE_NORMAL = "normal" + const val MAP_TYPE_HYBRID = "hybrid" + const val MAP_TYPE_TERRAIN = "terrain" + + const val FEATURE_ACTIVE = "park" + const val FEATURE_CRAVINGS = "restaurant" + const val FEATURE_EMERGENCY = "hospital" + const val FEATURE_SAFETY = "safety" + const val FEATURE_SHOPPING = "shopping" + + const val max_safe_elevation_mtr = 2590.8 + + val placeAPIFields = listOf( + Place.Field.ID, + Place.Field.NAME, + Place.Field.LAT_LNG, + Place.Field.ADDRESS + ) + + val LOCATION_PERMISSIONS = arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + + val featureColorMap = mapOf( + FEATURE_ACTIVE to android.R.color.holo_green_dark, + FEATURE_CRAVINGS to android.R.color.holo_orange_dark, + FEATURE_EMERGENCY to android.R.color.holo_blue_dark, + FEATURE_SAFETY to R.color.grey, + FEATURE_SHOPPING to R.color.baby_pink + ) + + val featureImageMap = mapOf( + FEATURE_ACTIVE to R.drawable.activity, + FEATURE_CRAVINGS to R.drawable.cravings, + FEATURE_EMERGENCY to R.drawable.emergency, + FEATURE_SAFETY to R.drawable.safety, + FEATURE_SHOPPING to R.drawable.shopping + ) + + val featureTextMap = mapOf( + FEATURE_ACTIVE to "Want\nrefreshment?", + FEATURE_CRAVINGS to "Any\nCravings?", + FEATURE_EMERGENCY to "Have\nEmergency?", + FEATURE_SAFETY to "Need\nSafety?", + FEATURE_SHOPPING to "Like\nto Shop?" + ) + + val routeColorList = listOf( + Color.argb(255, 241, 34, 104), //pink + Color.argb(255, 104, 241, 34), + Color.argb(255, 34, 104, 241), + Color.argb(255, 33, 243, 225), + Color.argb(255, 255, 235, 59), + Color.argb(255, 156, 39, 176), + Color.argb(255, 248, 59, 255) + ) + + val iconMarker = mapOf( + FEATURE_ACTIVE to R.drawable.marker_refresh, + FEATURE_CRAVINGS to R.drawable.marker_cravings, + FEATURE_EMERGENCY to R.drawable.marker_emergency, + FEATURE_SAFETY to R.drawable.marker_safety, + FEATURE_SHOPPING to R.drawable.marker_shopping + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/utility/Extensions.kt b/app/src/main/java/com/map/mom/utility/Extensions.kt new file mode 100644 index 00000000..e5417848 --- /dev/null +++ b/app/src/main/java/com/map/mom/utility/Extensions.kt @@ -0,0 +1,17 @@ +package com.map.mom.utility + +import android.content.Context +import android.widget.Toast +import com.google.android.gms.maps.GoogleMap + +fun GoogleMap.changeMapView(mapType: String) { + when (mapType) { + Constants.MAP_TYPE_NORMAL -> this.mapType = GoogleMap.MAP_TYPE_NORMAL + Constants.MAP_TYPE_HYBRID -> this.mapType = GoogleMap.MAP_TYPE_HYBRID + Constants.MAP_TYPE_TERRAIN -> this.mapType = GoogleMap.MAP_TYPE_TERRAIN + } +} + +fun Context.showToast(message: String){ + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/utility/FeatureUtility.kt b/app/src/main/java/com/map/mom/utility/FeatureUtility.kt new file mode 100644 index 00000000..943cb8dc --- /dev/null +++ b/app/src/main/java/com/map/mom/utility/FeatureUtility.kt @@ -0,0 +1,89 @@ +package com.map.mom.utility + +import com.google.android.gms.maps.model.Marker +import com.google.android.libraries.places.api.model.Place + +object FeatureUtility { + + fun getFeatureTypeFromName(featureName: String): List { + return when (featureName) { + Constants.FEATURE_ACTIVE -> { + arrayListOf( + Place.Type.PARK.toString().lowercase() + ) + } + + Constants.FEATURE_CRAVINGS -> { + arrayListOf( + Place.Type.RESTAURANT.toString().lowercase(), + Place.Type.CAFE.toString().lowercase(), + Place.Type.SUPERMARKET.toString().lowercase(), + Place.Type.MEAL_DELIVERY.toString().lowercase(), + Place.Type.MEAL_TAKEAWAY.toString().lowercase(), + Place.Type.FOOD.toString().lowercase() + ) + } + + Constants.FEATURE_EMERGENCY -> { + arrayListOf( + Place.Type.DOCTOR.toString().lowercase(), + Place.Type.PHARMACY.toString().lowercase(), + Place.Type.HOSPITAL.toString().lowercase() + ) + } + + Constants.FEATURE_SAFETY -> { + arrayListOf( + Place.Type.POLICE.toString().lowercase(), + Place.Type.FIRE_STATION.toString().lowercase() + ) + } + + Constants.FEATURE_SHOPPING -> { + arrayListOf( + Place.Type.SHOPPING_MALL.toString().lowercase(), + Place.Type.CLOTHING_STORE.toString().lowercase(), + Place.Type.JEWELRY_STORE.toString().lowercase() + ) + } + + else -> arrayListOf() + } + } + + fun getKeywordForFeatureType(featureName: String): String { + return when (featureName) { + Constants.FEATURE_ACTIVE -> { + "pregnancy|walking|garden|care|physiotherapy|walkway" + } + + Constants.FEATURE_CRAVINGS -> { + "food||healthy|health|juice|fruits|diet|salad|pregnancy" + } + + Constants.FEATURE_EMERGENCY -> { + "maternity|gynecologist|obstetrician|prasutigruh|women|pregnancy|pharmacy" + } + + Constants.FEATURE_SAFETY -> { + "" + } + + Constants.FEATURE_SHOPPING -> { + "baby|pregnant|maternity|women|kids" + } + + else -> "" + } + } + + fun removeAllFeatureMarkers(markerMap: MutableMap>, featureType: String) { + if (markerMap[featureType] != null) { + for (marker in markerMap[featureType]!!) { + marker.remove() + } + markerMap[featureType]!!.clear() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/map/mom/viewmodels/FeaturesViewModel.kt b/app/src/main/java/com/map/mom/viewmodels/FeaturesViewModel.kt new file mode 100644 index 00000000..bcba4ac4 --- /dev/null +++ b/app/src/main/java/com/map/mom/viewmodels/FeaturesViewModel.kt @@ -0,0 +1,28 @@ +package com.map.mom.viewmodels + +import androidx.lifecycle.ViewModel +import com.map.mom.utility.Constants +import com.map.mom.models.Feature +import com.map.mom.R + +class FeaturesViewModel: ViewModel() { + + private val _featureList = mutableListOf() + val featureList: List = _featureList + + init { + _featureList.add(Feature(Constants.FEATURE_ACTIVE, R.drawable.demo, false)) + _featureList.add(Feature(Constants.FEATURE_CRAVINGS, R.drawable.demo, false)) + _featureList.add(Feature(Constants.FEATURE_EMERGENCY, R.drawable.demo, false)) + _featureList.add(Feature(Constants.FEATURE_SAFETY, R.drawable.demo, false)) + _featureList.add(Feature(Constants.FEATURE_SHOPPING, R.drawable.demo, false)) + } + + fun onFeatureClick(position: Int) { + _featureList[position].isSelected = !_featureList[position].isSelected + } + + /*fun getSelectedFeatures(): List { + return _featureList.filter { it.isSelected } + }*/ +} \ No newline at end of file diff --git a/app/src/main/res/drawable/activity.png b/app/src/main/res/drawable/activity.png new file mode 100644 index 00000000..412f3201 Binary files /dev/null and b/app/src/main/res/drawable/activity.png differ diff --git a/app/src/main/res/drawable/cravings.png b/app/src/main/res/drawable/cravings.png new file mode 100644 index 00000000..5ca0703b Binary files /dev/null and b/app/src/main/res/drawable/cravings.png differ diff --git a/app/src/main/res/drawable/demo.png b/app/src/main/res/drawable/demo.png new file mode 100644 index 00000000..f3c42118 Binary files /dev/null and b/app/src/main/res/drawable/demo.png differ diff --git a/app/src/main/res/drawable/emergency.png b/app/src/main/res/drawable/emergency.png new file mode 100644 index 00000000..e6590904 Binary files /dev/null and b/app/src/main/res/drawable/emergency.png differ diff --git a/app/src/main/res/drawable/full_logo.png b/app/src/main/res/drawable/full_logo.png new file mode 100644 index 00000000..abd4232d Binary files /dev/null and b/app/src/main/res/drawable/full_logo.png differ diff --git a/app/src/main/res/drawable/ic_check_round.xml b/app/src/main/res/drawable/ic_check_round.xml new file mode 100644 index 00000000..bb8989d9 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_round.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_control_map_30.xml b/app/src/main/res/drawable/ic_control_map_30.xml new file mode 100644 index 00000000..74db22a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_control_map_30.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_navigation_30.xml b/app/src/main/res/drawable/ic_navigation_30.xml new file mode 100644 index 00000000..e4406060 --- /dev/null +++ b/app/src/main/res/drawable/ic_navigation_30.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/marker_cravings.png b/app/src/main/res/drawable/marker_cravings.png new file mode 100644 index 00000000..a8437e42 Binary files /dev/null and b/app/src/main/res/drawable/marker_cravings.png differ diff --git a/app/src/main/res/drawable/marker_current.png b/app/src/main/res/drawable/marker_current.png new file mode 100644 index 00000000..adc3a413 Binary files /dev/null and b/app/src/main/res/drawable/marker_current.png differ diff --git a/app/src/main/res/drawable/marker_emergency.png b/app/src/main/res/drawable/marker_emergency.png new file mode 100644 index 00000000..30b9a906 Binary files /dev/null and b/app/src/main/res/drawable/marker_emergency.png differ diff --git a/app/src/main/res/drawable/marker_refresh.png b/app/src/main/res/drawable/marker_refresh.png new file mode 100644 index 00000000..d8ce86b1 Binary files /dev/null and b/app/src/main/res/drawable/marker_refresh.png differ diff --git a/app/src/main/res/drawable/marker_safety.png b/app/src/main/res/drawable/marker_safety.png new file mode 100644 index 00000000..7d485941 Binary files /dev/null and b/app/src/main/res/drawable/marker_safety.png differ diff --git a/app/src/main/res/drawable/marker_shopping.png b/app/src/main/res/drawable/marker_shopping.png new file mode 100644 index 00000000..2f7bf25d Binary files /dev/null and b/app/src/main/res/drawable/marker_shopping.png differ diff --git a/app/src/main/res/drawable/outlined_circle_bg.xml b/app/src/main/res/drawable/outlined_circle_bg.xml new file mode 100644 index 00000000..f7318f15 --- /dev/null +++ b/app/src/main/res/drawable/outlined_circle_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/red_outlined_circle_bg.xml b/app/src/main/res/drawable/red_outlined_circle_bg.xml new file mode 100644 index 00000000..f5c34f5b --- /dev/null +++ b/app/src/main/res/drawable/red_outlined_circle_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/safety.png b/app/src/main/res/drawable/safety.png new file mode 100644 index 00000000..32e99e14 Binary files /dev/null and b/app/src/main/res/drawable/safety.png differ diff --git a/app/src/main/res/drawable/shopping.png b/app/src/main/res/drawable/shopping.png new file mode 100644 index 00000000..48651f38 Binary files /dev/null and b/app/src/main/res/drawable/shopping.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..8428ff52 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 00000000..22324511 --- /dev/null +++ b/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_street_view.xml b/app/src/main/res/layout/activity_street_view.xml new file mode 100644 index 00000000..05599707 --- /dev/null +++ b/app/src/main/res/layout/activity_street_view.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_feature.xml b/app/src/main/res/layout/item_feature.xml new file mode 100644 index 00000000..f5396439 --- /dev/null +++ b/app/src/main/res/layout/item_feature.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_route_details.xml b/app/src/main/res/layout/item_route_details.xml new file mode 100644 index 00000000..69054824 --- /dev/null +++ b/app/src/main/res/layout/item_route_details.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_auto_complete_search.xml b/app/src/main/res/layout/layout_auto_complete_search.xml new file mode 100644 index 00000000..573c8821 --- /dev/null +++ b/app/src/main/res/layout/layout_auto_complete_search.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_progressbar.xml b/app/src/main/res/layout/layout_progressbar.xml new file mode 100644 index 00000000..69f63e66 --- /dev/null +++ b/app/src/main/res/layout/layout_progressbar.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..4ae7d123 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..4ae7d123 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..f56c97cc Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 00000000..ff341fe9 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..a69e83be Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..a2f5aad3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..9cd04647 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 00000000..527f211f Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..3aa5bdd8 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..fef6dda0 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..89731759 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 00000000..316dfcbd Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..d03ce03a Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..d9fa3585 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..4ddd638d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..495412c8 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..fe54dee6 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..00a2b3fa Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..b17744d9 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..460440db Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..44ba4992 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..dfa2c363 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..c46337a6 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..8783acdf --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,22 @@ + + + #6C21DC + #80B4F6 + #352359 + #FF000000 + #FFFFFFFF + #000000 + #D3D3D3 + #5A5A5A + #FFFAFAFA + #F6F6F6 + #808080 + #2196F3 + #bfbfbf + #61000000 + #F12268 + #C60303 + #CD82CB + #9A9999 + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..aca8a5f9 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ + + + @dimen/_4sdp + @dimen/_8sdp + @dimen/_10sdp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..01194d61 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,15 @@ + + MOM + MainActivity + Change Map View + Navigation + Tap Anywhere on the Map for Roadside Perspective + MOM - Maternity Oriented Map + select route + + Feature Image + Please select destination first… + No Route found! + Search Destination here.. + Default Route + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..c46337a6 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 00000000..fa0f996d --- /dev/null +++ b/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..9ee9997b --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/map/mom/ExampleUnitTest.kt b/app/src/test/java/com/map/mom/ExampleUnitTest.kt new file mode 100644 index 00000000..f4112635 --- /dev/null +++ b/app/src/test/java/com/map/mom/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.map.mom + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..20d0696f --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,6 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id("com.android.application") version "8.0.2" apply false + id("org.jetbrains.kotlin.android") version "1.9.0" apply false + id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") version "2.0.1" apply false +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..3c5031eb --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..91b995db --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Oct 18 00:07:38 IST 2023 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..4f906e0c --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/images/logo.png b/images/logo.png new file mode 100644 index 00000000..2bc69909 Binary files /dev/null and b/images/logo.png differ diff --git a/images/screenshot1.png b/images/screenshot1.png new file mode 100644 index 00000000..c8f246ea Binary files /dev/null and b/images/screenshot1.png differ diff --git a/images/screenshot2.png b/images/screenshot2.png new file mode 100644 index 00000000..9c5ddd4e Binary files /dev/null and b/images/screenshot2.png differ diff --git a/images/usecase_diagram.png b/images/usecase_diagram.png new file mode 100644 index 00000000..e82fc15a Binary files /dev/null and b/images/usecase_diagram.png differ diff --git a/local.properties b/local.properties new file mode 100644 index 00000000..7b278893 --- /dev/null +++ b/local.properties @@ -0,0 +1,9 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Thu Oct 26 22:49:34 IST 2023 +MAPS_API_KEY= +sdk.dir=C\:\\Users\\BHOOMI\\AppData\\Local\\Android\\Sdk diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..1ce298a2 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,17 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "MOM" +include(":app")