diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c0afb7e..4f021b5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,8 +19,12 @@ jobs: java-version: '11' distribution: 'temurin' cache: gradle - - name: Build with Gradle - run: ./gradlew build + - name: Create local gradle properties files + run: touch local.properties + - name: Add Google Maps API Key + run: echo "MAPS_API_KEY=${{secrets.MAPS_API_KEY}}" >> local.properties + - name: Build debug APK + run: ./gradlew build -x lint -x lintVitalRelease - name: Upload APK uses: actions/upload-artifact@v3.1.3 with: diff --git a/app/build.gradle b/app/build.gradle index 02f9bb5..08e36af 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,8 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'kotlin-parcelize' + id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' + } android { @@ -53,4 +55,7 @@ dependencies { // GSON dependency implementation 'com.google.code.gson:gson:2.8.5' + + // Places API + implementation 'com.google.android.libraries.places:places:3.2.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 705de1f..40322fd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,10 @@ android:theme="@style/Theme.NLInterface" tools:targetApi="31"> + + - - + android:name=".activities.PlaceDetailsActivity" /> - diff --git a/app/src/main/java/com/nlinterface/activities/GroceryListActivity.kt b/app/src/main/java/com/nlinterface/activities/GroceryListActivity.kt index d04d520..c44f724 100644 --- a/app/src/main/java/com/nlinterface/activities/GroceryListActivity.kt +++ b/app/src/main/java/com/nlinterface/activities/GroceryListActivity.kt @@ -1,7 +1,5 @@ package com.nlinterface.activities -import android.app.AlertDialog -import android.content.res.Resources import android.os.Bundle import android.util.Log import android.view.View @@ -9,19 +7,21 @@ import android.view.WindowManager import android.widget.Button import android.widget.EditText import android.widget.ImageButton +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.dialog.MaterialDialogs import com.nlinterface.R import com.nlinterface.adapters.GroceryListAdapter import com.nlinterface.databinding.ActivityGroceryListBinding import com.nlinterface.dataclasses.GroceryItem import com.nlinterface.interfaces.GroceryListCallback import com.nlinterface.utility.GlobalParameters -import com.nlinterface.utility.SpeechToTextUtility -import com.nlinterface.utility.setViewRelativeHeight import com.nlinterface.utility.setViewRelativeSize import com.nlinterface.viewmodels.GroceryListViewModel @@ -73,6 +73,48 @@ class GroceryListActivity : AppCompatActivity(), GroceryListCallback { voiceActivationButton.setOnClickListener { onAddVoiceActivationButtonClick() } + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + 0, ItemTouchHelper.LEFT + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean {return false} + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val groceryItem: GroceryItem = + groceryItemList[viewHolder.adapterPosition] + + val index = viewHolder.adapterPosition + + viewModel.deleteGroceryItem(groceryItem) + + adapter.notifyItemRemoved(index) + } + }).attachToRecyclerView(rvGroceryList) + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + 0, ItemTouchHelper.RIGHT + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean {return false} + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val groceryItem: GroceryItem = + groceryItemList[viewHolder.adapterPosition] + + val index = viewHolder.adapterPosition + + viewModel.deleteGroceryItem(groceryItem) + + adapter.notifyItemRemoved(index) + } + }).attachToRecyclerView(rvGroceryList) } override fun onDestroy() { @@ -86,14 +128,16 @@ class GroceryListActivity : AppCompatActivity(), GroceryListCallback { private fun onAddItemButtonClick() { - val alertDialog: AlertDialog? = this?.let { - val builder = AlertDialog.Builder(it) - val view = layoutInflater.inflate(R.layout.add_item_dialog, null) + val alertDialog: AlertDialog = this.let { + val builder = MaterialAlertDialogBuilder(it, R.style.ThemeOverlay_MaterialComponents_MaterialAlertDialog_Background) + + val view = layoutInflater.inflate(R.layout.edit_text_dialog, null) + builder.setView(view) builder.apply { setPositiveButton(R.string.add) { _, _ -> - val addItemEt = view.findViewById(R.id.add_item_et) + val addItemEt = view.findViewById(R.id.et) val newItemName = addItemEt.text.toString() viewModel.addGroceryItem(newItemName) @@ -103,17 +147,20 @@ class GroceryListActivity : AppCompatActivity(), GroceryListCallback { setNegativeButton(R.string.cancel) { _, _ -> } } // Set other dialog properties - builder.setMessage(R.string.add_new_grocery_item) + builder.setTitle(R.string.add_new_grocery_item) // Create the AlertDialog builder.create() } - alertDialog?.show() + alertDialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + alertDialog.show() } + + override fun onLongClick(item: GroceryItem) { val index = groceryItemList.indexOf(item) - viewModel.deleteGroceryItem(item) - adapter?.notifyItemRemoved(index) + viewModel.placeGroceryItemInCart(item) + adapter.notifyItemChanged(index) } } \ No newline at end of file diff --git a/app/src/main/java/com/nlinterface/activities/ItemSavedLocation.kt b/app/src/main/java/com/nlinterface/activities/ItemSavedLocation.kt deleted file mode 100644 index 64c57a9..0000000 --- a/app/src/main/java/com/nlinterface/activities/ItemSavedLocation.kt +++ /dev/null @@ -1,116 +0,0 @@ -package com.nlinterface.activities - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.TextView -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.nlinterface.R - -class SavedLocationsActivity : AppCompatActivity() { - - private lateinit var savedLocationsRecyclerView: RecyclerView - private lateinit var savedLocationsAdapter: SavedLocationsAdapter - private lateinit var emptyTextView: TextView - - private var savedLocations: MutableList = mutableListOf() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_saved_locations) - - savedLocationsRecyclerView = findViewById(R.id.savedLocationsRecyclerView) - emptyTextView = findViewById(R.id.emptyTextView) - - // Retrieve the saved locations from the intent - val intentLocations = intent.getStringArrayListExtra("savedLocations") - savedLocations = intentLocations?.toMutableList() ?: mutableListOf() - - savedLocationsAdapter = SavedLocationsAdapter(savedLocations, - onItemClick = { selectedLocation -> - openMaps(selectedLocation) - }, - onDeleteClick = { locationToDelete -> - deleteLocation(locationToDelete) - } - ) - - savedLocationsRecyclerView.adapter = savedLocationsAdapter - savedLocationsRecyclerView.layoutManager = LinearLayoutManager(this) - - updateEmptyTextViewVisibility() - } - - private fun openMaps(address: String) { - val gmmIntentUri = Uri.parse("geo:0,0?q=${Uri.encode(address)}") - val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri) - mapIntent.setPackage("com.google.android.apps.maps") - - if (mapIntent.resolveActivity(packageManager) != null) { - startActivity(mapIntent) - } else { - Toast.makeText( - this, - "Google Maps is not installed on your device", - Toast.LENGTH_SHORT - ).show() - } - } - - private fun deleteLocation(location: String) { - savedLocations.remove(location) - savedLocationsAdapter.notifyDataSetChanged() - - // Update the saved locations in SharedPreferences after deleting - SharedPreferencesHelper.saveLocations(this, savedLocations) - - updateEmptyTextViewVisibility() - } - - private fun updateEmptyTextViewVisibility() { - if (savedLocations.isEmpty()) { - emptyTextView.visibility = View.VISIBLE - } else { - emptyTextView.visibility = View.GONE - } - } - - inner class SavedLocationsAdapter( - private val locations: MutableList, - private val onItemClick: (String) -> Unit, - private val onDeleteClick: (String) -> Unit - ) : RecyclerView.Adapter() { - - inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val locationButton: Button = itemView.findViewById(R.id.locationButton) - val deleteButton: Button = itemView.findViewById(R.id.deleteButton) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val itemView = layoutInflater.inflate(R.layout.item_saved_location, parent, false) - return ViewHolder(itemView) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val location = locations[position] - holder.locationButton.text = location - holder.locationButton.setOnClickListener { - onItemClick(location) - } - - holder.deleteButton.setOnClickListener { - onDeleteClick(location) - } - } - - override fun getItemCount(): Int { - return locations.size - } - } -} diff --git a/app/src/main/java/com/nlinterface/activities/LocationActivity.kt b/app/src/main/java/com/nlinterface/activities/LocationActivity.kt deleted file mode 100644 index 2bdd2ec..0000000 --- a/app/src/main/java/com/nlinterface/activities/LocationActivity.kt +++ /dev/null @@ -1,114 +0,0 @@ -package com.nlinterface.activities - -import android.Manifest -import android.annotation.SuppressLint -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.location.Location -import android.location.LocationListener -import android.location.LocationManager -import android.net.Uri -import android.os.Bundle -import android.view.WindowManager -import android.widget.Button -import android.widget.TextView -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import com.nlinterface.R -import com.nlinterface.utility.GlobalParameters - - -class LocationActivity : AppCompatActivity(), LocationListener { - - private lateinit var locationManager: LocationManager - private lateinit var tvGpsLocation: TextView - private val locationPermissionCode = 2 - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - // process keep screen on settings - if (GlobalParameters.instance!!.keepScreenOnSwitch == GlobalParameters.KeepScreenOn.YES) { - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - - setContentView(R.layout.activity_location) - title = "Location App" - - // If Get Location button is click, run getLocation() - val getLocationButton: Button = findViewById(R.id.getLocation) - getLocationButton.setOnClickListener { - getLocation() - } - } - - private fun getLocation() { - - // Get the location manager - locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager - - // If location tracking permission is not granted yet, ask for permission - if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)) { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), locationPermissionCode) - } - - // Requests location - // Can be updated after 5s and if the distance to the previous locations is more than 5m? - locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 5f, this) - } - - // Display the coordinates in the app - @SuppressLint("SetTextI18n") - override fun onLocationChanged(location: Location) { - tvGpsLocation = findViewById(R.id.textView) - tvGpsLocation.text = "Latitude: " + location.latitude + "\nLongitude: " + location.longitude - - // When the Google Maps button is clicked, the current location data will be - // opened in Google Maps - val googleMapsBtn: Button = findViewById(R.id.googleMapsLocationBtn) - googleMapsBtn.setOnClickListener { - - // Open Google Maps with specific location data - val uri = "http://maps.google.com/maps?daddr=" + location.latitude + ", " + location.longitude - - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) - intent.setPackage("com.google.android.apps.maps") - - // try to open Google maps, if not installed, show please install message - try { - startActivity(intent) - } catch (ex: ActivityNotFoundException) { - try { - val unrestrictedIntent = Intent(Intent.ACTION_VIEW, Uri.parse(uri)) - startActivity(unrestrictedIntent) - } catch (innerEx: ActivityNotFoundException) { - Toast.makeText(this, "Please install a maps application", Toast.LENGTH_LONG).show() - } - } - } - - } - - // TO DO understand code and describe it - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - - if (requestCode == locationPermissionCode) { - - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show() - } - - else { - Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show() - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nlinterface/activities/MainActivity.kt b/app/src/main/java/com/nlinterface/activities/MainActivity.kt index 978c6e2..896934a 100644 --- a/app/src/main/java/com/nlinterface/activities/MainActivity.kt +++ b/app/src/main/java/com/nlinterface/activities/MainActivity.kt @@ -44,18 +44,12 @@ class MainActivity : AppCompatActivity() { view.context.startActivity(intent) } - val navigationActivityButton: Button = findViewById(R.id.navigation_bt) as Button - navigationActivityButton.setOnClickListener { view -> - val intent = Intent(view.context, NavigationActivity::class.java) + val placeDetailsButton: Button = findViewById(R.id.place_details_bt) as Button + placeDetailsButton.setOnClickListener { view -> + val intent = Intent(view.context, PlaceDetailsActivity::class.java) view.context.startActivity(intent) } - val buttonClickMe: Button = findViewById(R.id.motor_module_bt) as Button - buttonClickMe.setOnClickListener { - val intent = Intent(this, MotorModule::class.java) - startActivity(intent) - } - val voiceActivationButton = findViewById(R.id.voice_activation_bt) as ImageButton setViewRelativeSize(voiceActivationButton, 1.0, 0.33) @@ -63,12 +57,11 @@ class MainActivity : AppCompatActivity() { onAddVoiceActivationButtonClick() } - - /*val settingsActivityButton: Button = findViewById(R.id.settings_bt) as Button + val settingsActivityButton: Button = findViewById(R.id.settings_bt) as Button settingsActivityButton.setOnClickListener { view -> val intent = Intent(view.context, SettingsActivity::class.java) view.context.startActivity(intent) - }*/ + } verifyAudioPermissions() } diff --git a/app/src/main/java/com/nlinterface/activities/MotorModule.kt b/app/src/main/java/com/nlinterface/activities/MotorModule.kt deleted file mode 100644 index 8be4d43..0000000 --- a/app/src/main/java/com/nlinterface/activities/MotorModule.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.nlinterface.activities - -import android.os.Bundle -import android.view.View -import android.widget.Button -import android.widget.EditText -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import com.nlinterface.R - -class MotorModule: AppCompatActivity() { - private lateinit var angleInput: EditText - private lateinit var calculateButton: Button - private lateinit var directionText: TextView - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_motor_module) - - angleInput = findViewById(R.id.angleInput) - calculateButton = findViewById(R.id.calculateButton) - directionText = findViewById(R.id.directionText) - - calculateButton.setOnClickListener { - val angleString = angleInput.text.toString() - if (angleString.isNotEmpty()) { - val angle = angleString.toDouble() - val direction = calculateDirection(angle) - directionText.text = direction - } - } - } - - private fun calculateDirection(angle: Double): String { - return when { - angle >= 0 && angle < 90 -> "Right" - angle >= 90 && angle < 180 -> "Left" - angle >= 180 && angle < 270 -> "Up" - angle >= 270 && angle < 360 -> "Right" - else -> "Invalid angle" - } - } -} diff --git a/app/src/main/java/com/nlinterface/activities/NavigationActivity.kt b/app/src/main/java/com/nlinterface/activities/NavigationActivity.kt deleted file mode 100644 index 6302dc8..0000000 --- a/app/src/main/java/com/nlinterface/activities/NavigationActivity.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.nlinterface.activities - -import android.annotation.SuppressLint -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.WindowManager -import android.widget.Button -import android.widget.EditText -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import com.nlinterface.R -import com.nlinterface.R.* -import com.nlinterface.utility.GlobalParameters - - -class NavigationActivity : AppCompatActivity() { - - private lateinit var addressEditText: EditText - private lateinit var openMapsButton: Button - private lateinit var saveLocationButton: Button - private lateinit var openSavedLocationsButton: Button - - object SavedLocationsDataSource { - val savedLocations: MutableList = mutableListOf() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.navigation_activity) - - // process keep screen on settings - if (GlobalParameters.instance!!.keepScreenOnSwitch == GlobalParameters.KeepScreenOn.YES) { - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - - addressEditText = findViewById(R.id.addressEditText) - openMapsButton = findViewById(R.id.openMapsButton) - saveLocationButton = findViewById(R.id.saveLocationButton) - openSavedLocationsButton = findViewById(R.id.openSavedLocationsButton) - - openMapsButton.setOnClickListener { - val address = addressEditText.text.toString() - openMaps(address) - } - - saveLocationButton.setOnClickListener { - val address = addressEditText.text.toString() - saveLocation(address) - } - - openSavedLocationsButton.setOnClickListener { - openSavedLocations() - } - - // Retrieve the saved locations from SharedPreferences on app launch - SavedLocationsDataSource.savedLocations.clear() - SavedLocationsDataSource.savedLocations.addAll(SharedPreferencesHelper.getSavedLocations(this)) - } - - private fun openMaps(address: String) { - val gmmIntentUri = Uri.parse("geo:0,0?q=${Uri.encode(address)}") - val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri) - mapIntent.setPackage("com.google.android.apps.maps") - - if (mapIntent.resolveActivity(packageManager) != null) { - startActivity(mapIntent) - } else { - Toast.makeText( - this, - "Google Maps is not installed on your device", - Toast.LENGTH_SHORT - ).show() - } - } - private fun saveLocation(address: String) { - SavedLocationsDataSource.savedLocations.add(address) - Toast.makeText( - this, - "Location saved: $address", - Toast.LENGTH_SHORT - ).show() - - // Save the updated list of locations to SharedPreferences - SharedPreferencesHelper.saveLocations(this, SavedLocationsDataSource.savedLocations) - - val intent = Intent(this, SavedLocationsActivity::class.java) - intent.putStringArrayListExtra("savedLocations", ArrayList(SavedLocationsDataSource.savedLocations)) - startActivity(intent) - } - - private fun openSavedLocations() { - val intent = Intent(this, SavedLocationsActivity::class.java) - intent.putStringArrayListExtra("savedLocations", ArrayList(SavedLocationsDataSource.savedLocations)) - startActivity(intent) - } -} diff --git a/app/src/main/java/com/nlinterface/activities/NextActivityExample.kt b/app/src/main/java/com/nlinterface/activities/NextActivityExample.kt deleted file mode 100644 index 8cc29d3..0000000 --- a/app/src/main/java/com/nlinterface/activities/NextActivityExample.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.nlinterface.activities - -import android.content.Intent -import android.os.Bundle -import android.view.View -import android.view.WindowManager -import android.widget.Button -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.WindowCompat -import com.nlinterface.databinding.ActivityMainBinding -import com.nlinterface.databinding.ActivityNextActivityExampleBinding -import com.nlinterface.utility.GlobalParameters - -class NextActivityExample : AppCompatActivity() { - - private lateinit var binding: ActivityNextActivityExampleBinding - - override fun onCreate(savedInstanceState: Bundle?) { - WindowCompat.setDecorFitsSystemWindows(window, false) - super.onCreate(savedInstanceState) - - binding = ActivityNextActivityExampleBinding.inflate(layoutInflater) - setContentView(binding.root) - - // process keep screen on settings - if (GlobalParameters.instance!!.keepScreenOnSwitch == GlobalParameters.KeepScreenOn.YES) { - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nlinterface/activities/PlaceDetailsActivity.kt b/app/src/main/java/com/nlinterface/activities/PlaceDetailsActivity.kt new file mode 100644 index 0000000..292b80b --- /dev/null +++ b/app/src/main/java/com/nlinterface/activities/PlaceDetailsActivity.kt @@ -0,0 +1,125 @@ +package com.nlinterface.activities + +import android.os.Bundle +import android.view.View +import android.widget.ImageButton +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.gms.common.api.Status +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.nlinterface.R +import com.nlinterface.adapters.PlaceDetailsAdapter +import com.nlinterface.databinding.ActivityPlaceDetailsBinding +import com.nlinterface.dataclasses.PlaceDetailsItem +import com.nlinterface.interfaces.PlaceDetailsItemCallback +import com.nlinterface.utility.setViewRelativeSize +import com.nlinterface.viewmodels.PlaceDetailsViewModel + + +class PlaceDetailsActivity: AppCompatActivity(), PlaceDetailsItemCallback { + + private lateinit var binding: ActivityPlaceDetailsBinding + private lateinit var viewModel: PlaceDetailsViewModel + private lateinit var placeDetailsItemList: ArrayList + private lateinit var adapter: PlaceDetailsAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + viewModel = ViewModelProvider(this)[PlaceDetailsViewModel::class.java] + viewModel.initPlaceClient(this) + viewModel.fetchPlaceDetailsItemList() + + binding = ActivityPlaceDetailsBinding.inflate(layoutInflater) + setContentView(binding.root) + + val rvPlaceDetails = findViewById(R.id.place_details_rv) as RecyclerView + placeDetailsItemList = viewModel.placeDetailsItemList + + adapter = PlaceDetailsAdapter(placeDetailsItemList, this) + rvPlaceDetails.adapter = adapter + rvPlaceDetails.layoutManager = LinearLayoutManager(this) + + rvPlaceDetails.itemAnimator?.changeDuration = 0 + + val voiceActivationButton = findViewById(R.id.voice_activation_bt) as ImageButton + setViewRelativeSize(voiceActivationButton, 1.0, 0.33) + + voiceActivationButton.setOnClickListener {} // TODO + + // Initialize the AutocompleteSupportFragment. + val autocompleteFragment = + supportFragmentManager.findFragmentById(R.id.autocomplete_fragment) + as AutocompleteSupportFragment + + // Specify the types of place data to return. + autocompleteFragment.setPlaceFields(listOf(Place.Field.ID)) + autocompleteFragment.setTypesFilter(listOf("supermarket")) + + // Set up a PlaceSelectionListener to handle the response. + autocompleteFragment.setOnPlaceSelectedListener(object : PlaceSelectionListener { + override fun onError(status: Status) { + viewModel.onError(status) + } + override fun onPlaceSelected(place: Place) { + viewModel.onPlaceSelected(place) { if (it) adapter.notifyItemInserted(placeDetailsItemList.size - 1) } + } + }) + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + 0, ItemTouchHelper.LEFT + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean {return false} + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val placeDetailsItem: PlaceDetailsItem = + placeDetailsItemList[viewHolder.adapterPosition] + + val index = viewHolder.adapterPosition + + viewModel.deletePlaceDetailsItem(placeDetailsItem) + adapter.notifyItemRemoved(index) + } + }).attachToRecyclerView(rvPlaceDetails) + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + 0, ItemTouchHelper.RIGHT + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean {return false} + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val placeDetailsItem: PlaceDetailsItem = + placeDetailsItemList[viewHolder.adapterPosition] + + val index = viewHolder.adapterPosition + + viewModel.deletePlaceDetailsItem(placeDetailsItem) + adapter.notifyItemRemoved(index) + } + }).attachToRecyclerView(rvPlaceDetails) + } + + override fun onDestroy() { + super.onDestroy() + viewModel.storePlaceDetailsItemList() + } + + override fun onClick(item: PlaceDetailsItem) { + val index = placeDetailsItemList.indexOf(item) + viewModel.changeFavorite(item) + adapter.notifyItemChanged(index) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nlinterface/activities/SavedLocationsDataSource.kt b/app/src/main/java/com/nlinterface/activities/SavedLocationsDataSource.kt deleted file mode 100644 index 23249ab..0000000 --- a/app/src/main/java/com/nlinterface/activities/SavedLocationsDataSource.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.nlinterface.activities - -class SavedLocationsDataSource { - val savedLocations: MutableList = mutableListOf() -} \ No newline at end of file diff --git a/app/src/main/java/com/nlinterface/activities/SettingsActivity.kt b/app/src/main/java/com/nlinterface/activities/SettingsActivity.kt index a6d2fb7..4f5851e 100644 --- a/app/src/main/java/com/nlinterface/activities/SettingsActivity.kt +++ b/app/src/main/java/com/nlinterface/activities/SettingsActivity.kt @@ -3,14 +3,17 @@ package com.nlinterface.activities import android.content.Context import android.os.Bundle import android.provider.Settings.Global +import android.view.View import android.view.WindowManager import android.widget.Button +import android.widget.ImageButton import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.nlinterface.R import com.nlinterface.databinding.ActivitySettingsBinding import com.nlinterface.utility.GlobalParameters +import com.nlinterface.utility.setViewRelativeSize class SettingsActivity : AppCompatActivity() { @@ -49,7 +52,8 @@ class SettingsActivity : AppCompatActivity() { window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } - header = findViewById(R.id.header) + val voiceActivationButton = findViewById(R.id.voice_activation_bt) as ImageButton + setViewRelativeSize(voiceActivationButton, 1.0, 0.33) /*impairmentOptions = mutableListOf() resources.getStringArray(R.array.impairment_options).forEach { option -> diff --git a/app/src/main/java/com/nlinterface/activities/SharedPreferencesHelper.kt b/app/src/main/java/com/nlinterface/activities/SharedPreferencesHelper.kt deleted file mode 100644 index 07a16b4..0000000 --- a/app/src/main/java/com/nlinterface/activities/SharedPreferencesHelper.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.nlinterface.activities - -import android.content.Context - -object SharedPreferencesHelper { - private const val SHARED_PREF_NAME = "SavedLocations" - - fun saveLocations(context: Context, locations: List) { - val sharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) - val editor = sharedPreferences.edit() - editor.putStringSet("locations", locations.toSet()) - editor.apply() - } - - fun getSavedLocations(context: Context): List { - val sharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) - return sharedPreferences.getStringSet("locations", emptySet())?.toList() ?: emptyList() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nlinterface/adapters/GroceryListAdapter.kt b/app/src/main/java/com/nlinterface/adapters/GroceryListAdapter.kt index 510fd86..87304d4 100644 --- a/app/src/main/java/com/nlinterface/adapters/GroceryListAdapter.kt +++ b/app/src/main/java/com/nlinterface/adapters/GroceryListAdapter.kt @@ -1,27 +1,36 @@ package com.nlinterface.adapters +import android.content.res.TypedArray import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.cardview.widget.CardView +import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import com.nlinterface.R import com.nlinterface.dataclasses.GroceryItem import com.nlinterface.interfaces.GroceryListCallback + class GroceryListAdapter( private val data: ArrayList, - private val groceryListCallback: GroceryListCallback + private val groceryListCallback: GroceryListCallback, ) : RecyclerView.Adapter() { + private lateinit var parent: ViewGroup + class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) { - val groceryItemView: TextView = itemView.findViewById(R.id.grocery_item_tv) + val groceryItemTextView: TextView = itemView.findViewById(R.id.grocery_item_tv) + val groceryItemCardView: CardView = itemView.findViewById(R.id.grocery_item_cv) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val context = parent.context + this.parent = parent + val context = this.parent.context val inflater = LayoutInflater.from(context) val groceryItemView = inflater.inflate(R.layout.grocery_item, parent, false) @@ -31,11 +40,30 @@ class GroceryListAdapter( override fun onBindViewHolder(holder: ViewHolder, position: Int) { val groceryItem: GroceryItem = data[position] // Set item views based on your views and data model - val textView = holder.groceryItemView + val textView = holder.groceryItemTextView + val cardView = holder.groceryItemCardView + val res = holder.itemView.resources textView.text = groceryItem.itemName - textView.setOnLongClickListener { - groceryListCallback.onLongClick(data[position]) + if (groceryItem.inCart) { + val attrs = intArrayOf(androidx.appcompat.R.attr.colorAccent) + val ta: TypedArray = parent.context.obtainStyledAttributes(attrs) + val color = ta.getResourceId(0, android.R.color.white) + ta.recycle() + cardView.background.setTint(ContextCompat.getColor(holder.itemView.context, color)) + } else { + val attrs = intArrayOf(androidx.appcompat.R.attr.colorPrimary) + val ta: TypedArray = parent.context.obtainStyledAttributes(attrs) + val color = ta.getResourceId(0, android.R.color.white) + ta.recycle() + cardView.background.setTint(ContextCompat.getColor(holder.itemView.context, color)) + } + + textView.layoutParams.width = LayoutParams.WRAP_CONTENT + cardView.layoutParams.width = LayoutParams.WRAP_CONTENT + + cardView.setOnLongClickListener { + groceryListCallback.onLongClick(data[holder.adapterPosition]) true } } diff --git a/app/src/main/java/com/nlinterface/adapters/PlaceDetailsAdapter.kt b/app/src/main/java/com/nlinterface/adapters/PlaceDetailsAdapter.kt new file mode 100644 index 0000000..8d5f640 --- /dev/null +++ b/app/src/main/java/com/nlinterface/adapters/PlaceDetailsAdapter.kt @@ -0,0 +1,76 @@ +package com.nlinterface.adapters + +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.nlinterface.R +import com.nlinterface.dataclasses.PlaceDetailsItem +import com.nlinterface.interfaces.PlaceDetailsItemCallback +import com.nlinterface.utility.setViewRelativeSize +import com.nlinterface.utility.setViewRelativeWidth +import java.text.SimpleDateFormat +import java.time.LocalDateTime +import java.util.Calendar + +class PlaceDetailsAdapter ( + private val data: ArrayList, + private val placeDetailsItemCallback: PlaceDetailsItemCallback +) : RecyclerView.Adapter() { + + class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) { + + val placeDetailsItemStoreNameTextView: TextView = itemView.findViewById(R.id.place_details_store_name_tv) + val placeDetailsItemOpeningHoursTextView: TextView = itemView.findViewById(R.id.place_details_opening_hours_tv) + val placeDetailsItemFavoriteImageView: ImageView = itemView.findViewById(R.id.place_details_favorite_iv) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val context = parent.context + val inflater = LayoutInflater.from(context) + val placeDetailsView = inflater.inflate(R.layout.place_detail_item, parent, false) + + return ViewHolder(placeDetailsView) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val placeDetailsItem: PlaceDetailsItem = data[position] + + val storeNameTextView = holder.placeDetailsItemStoreNameTextView + val openingHoursTextView = holder.placeDetailsItemOpeningHoursTextView + + Log.println(Log.DEBUG, "openinghours", placeDetailsItem.openingHours.toString()) + val favoriteImageView = holder.placeDetailsItemFavoriteImageView + + val res = holder.itemView.resources + + storeNameTextView.text = placeDetailsItem.storeName + + val dayOfWeek = Math.floorMod(Calendar.getInstance().get(Calendar.DAY_OF_WEEK) - 2, 7) + + val regex = "(?<=: \\[?)(\\w:?–? ?ö?)+( ?–?\\d?:?)+".toRegex() + //val regex = "(?<=:\\s\\[)[\\d:– ]*(?=.*\\])|(?<=:\\s)[^\\[\\n]*".toRegex() + Log.println(Log.DEBUG, "regex", regex.toString()) + val openingHoursText = regex.find(placeDetailsItem.openingHours[dayOfWeek])?.value + Log.println(Log.DEBUG, "openinghourstext", openingHoursText.toString()) + openingHoursTextView.text = openingHoursText + + if (placeDetailsItem.favorite) { + holder.placeDetailsItemFavoriteImageView.setImageResource(R.drawable.ic_baseline_star_24) + } else { + holder.placeDetailsItemFavoriteImageView.setImageResource(R.drawable.ic_baseline_star_border_24) + } + + favoriteImageView.setOnClickListener { + placeDetailsItemCallback.onClick(data[holder.adapterPosition]) + } + } + + override fun getItemCount(): Int { + return data.size + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/nlinterface/dataclasses/PlaceDetailsItem.kt b/app/src/main/java/com/nlinterface/dataclasses/PlaceDetailsItem.kt new file mode 100644 index 0000000..ae1d3a3 --- /dev/null +++ b/app/src/main/java/com/nlinterface/dataclasses/PlaceDetailsItem.kt @@ -0,0 +1,17 @@ +package com.nlinterface.dataclasses + +import android.os.Parcelable +import com.google.android.libraries.places.api.model.OpeningHours +import kotlinx.parcelize.Parcelize + +@Parcelize +data class PlaceDetailsItem( + + val placeID: String, + val storeName: String, + val openingHours: List, + var favorite: Boolean + +): Parcelable { + +} \ No newline at end of file diff --git a/app/src/main/java/com/nlinterface/interfaces/PlaceDetailsItemCallback.kt b/app/src/main/java/com/nlinterface/interfaces/PlaceDetailsItemCallback.kt new file mode 100644 index 0000000..c4e70d8 --- /dev/null +++ b/app/src/main/java/com/nlinterface/interfaces/PlaceDetailsItemCallback.kt @@ -0,0 +1,6 @@ +package com.nlinterface.interfaces +import com.nlinterface.dataclasses.PlaceDetailsItem + +interface PlaceDetailsItemCallback { + fun onClick(item: PlaceDetailsItem) +} \ No newline at end of file diff --git a/app/src/main/java/com/nlinterface/utility/utils.kt b/app/src/main/java/com/nlinterface/utility/utils.kt index 4ba55d6..4d1fd53 100644 --- a/app/src/main/java/com/nlinterface/utility/utils.kt +++ b/app/src/main/java/com/nlinterface/utility/utils.kt @@ -1,8 +1,18 @@ package com.nlinterface.utility +import android.content.Context import android.content.res.Resources +import android.os.Parcelable +import android.util.Log import android.view.View +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt import androidx.constraintlayout.widget.ConstraintLayout +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.nlinterface.dataclasses.GroceryItem +import java.io.BufferedReader +import java.io.File fun setViewRelativeWidth(view: View, relWidth: Double) { val width: Int = (Resources.getSystem().displayMetrics.widthPixels * relWidth).toInt() @@ -18,4 +28,4 @@ fun setViewRelativeSize(view: View, relWidth:Double, relHeight: Double) { val height = (Resources.getSystem().displayMetrics.heightPixels * relHeight).toInt() val width = (Resources.getSystem().displayMetrics.widthPixels * relWidth).toInt() view.layoutParams = ConstraintLayout.LayoutParams(width, height) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/nlinterface/viewmodels/GroceryListViewModel.kt b/app/src/main/java/com/nlinterface/viewmodels/GroceryListViewModel.kt index f565865..72936d3 100644 --- a/app/src/main/java/com/nlinterface/viewmodels/GroceryListViewModel.kt +++ b/app/src/main/java/com/nlinterface/viewmodels/GroceryListViewModel.kt @@ -47,6 +47,10 @@ class GroceryListViewModel ( groceryList.remove(groceryItem) } + fun placeGroceryItemInCart(groceryItem: GroceryItem) { + groceryItem.inCart = !groceryItem.inCart + } + fun storeGroceryList() { val jsonString : String = gson.toJson(groceryList) groceryListFile.writeText(jsonString) diff --git a/app/src/main/java/com/nlinterface/viewmodels/PlaceDetailsViewModel.kt b/app/src/main/java/com/nlinterface/viewmodels/PlaceDetailsViewModel.kt new file mode 100644 index 0000000..31d1a48 --- /dev/null +++ b/app/src/main/java/com/nlinterface/viewmodels/PlaceDetailsViewModel.kt @@ -0,0 +1,111 @@ +package com.nlinterface.viewmodels + +import android.app.Application +import android.content.Context +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.common.api.Status +import com.google.android.libraries.places.api.Places +import com.google.android.libraries.places.api.model.OpeningHours +import com.google.android.libraries.places.api.model.Place +import com.google.android.libraries.places.api.net.FetchPlaceRequest +import com.google.android.libraries.places.api.net.FetchPlaceResponse +import com.google.android.libraries.places.api.net.PlacesClient +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.nlinterface.BuildConfig +import com.nlinterface.dataclasses.GroceryItem +import com.nlinterface.dataclasses.PlaceDetailsItem +import kotlinx.coroutines.CompletionHandler +import java.io.BufferedReader +import java.io.File + +class PlaceDetailsViewModel(application: Application) : AndroidViewModel(application) { + + private val context = application + private lateinit var placesClient: PlacesClient + + private val placeDetailsItemListFileName = "PlaceDetailsItemList.json" + private val placeDetailsItemListFile: File = File(context.filesDir, placeDetailsItemListFileName) + var placeDetailsItemList: ArrayList = ArrayList () + var gson = Gson() + + fun fetchPlaceDetailsItemList() { + + if (!placeDetailsItemListFile.exists()) { + placeDetailsItemListFile.createNewFile() + } + + if (placeDetailsItemListFile.length() > 0) { + val bufferedReader: BufferedReader = placeDetailsItemListFile.bufferedReader() + val inputString = bufferedReader.use { it.readText() } + placeDetailsItemList = gson.fromJson( + inputString, + object : TypeToken?>() {}.type + ) as ArrayList + } + + Log.println(Log.DEBUG, "1", placeDetailsItemList.toString()) + } + + private fun addPlaceDetailsItem(placeID: String, storeName: String, openingHours: List): ArrayList { + placeDetailsItemList.add(PlaceDetailsItem(placeID, storeName, openingHours, false)) + return placeDetailsItemList + } + + fun initPlaceClient(context: Context) { + Places.initialize(context, BuildConfig.MAPS_API_KEY) + placesClient = Places.createClient(context) + } + + fun onPlaceSelected(place: Place, completion: (success: Boolean) -> Unit) { + val placeID = place.id + fetchPlaceDetails(placeID) { + completion(it) + } + } + + private fun fetchPlaceDetails(placeID: String?, completion: (success: Boolean) -> Unit) { + + // Specify the fields to return. + val placeFields = listOf( + Place.Field.NAME, Place.Field.OPENING_HOURS, Place.Field.ADDRESS + ) + + // Construct a request object, passing the place ID and fields array. + val request = placeID?.let { FetchPlaceRequest.newInstance(it, placeFields) } + + if (request != null) { + placesClient.fetchPlace(request) + .addOnSuccessListener { response: FetchPlaceResponse -> + addPlaceDetailsItem(placeID, response.place.name, response.place.openingHours.weekdayText) + completion(true) + }.addOnFailureListener { exception: Exception -> + if (exception is ApiException) { + Log.println(Log.DEBUG, "MAPSAPI", "failed") + } + completion(false) + } + } + } + + fun onError(status: Status) { + // TODO + } + + fun storePlaceDetailsItemList() { + val jsonString : String = gson.toJson(placeDetailsItemList) + placeDetailsItemListFile.writeText(jsonString) + } + + fun changeFavorite(item: PlaceDetailsItem) { + item.favorite = !item.favorite + } + + fun deletePlaceDetailsItem(placeDetailsItem: PlaceDetailsItem): ArrayList { + placeDetailsItemList.remove(placeDetailsItem) + return placeDetailsItemList + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_star_24.xml b/app/src/main/res/drawable/ic_baseline_star_24.xml new file mode 100644 index 0000000..1a8a340 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_star_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_star_border_24.xml b/app/src/main/res/drawable/ic_baseline_star_border_24.xml new file mode 100644 index 0000000..d4b713b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_star_border_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_grocery_list.xml b/app/src/main/res/layout/activity_grocery_list.xml index 66ee25f..95cfb7e 100644 --- a/app/src/main/res/layout/activity_grocery_list.xml +++ b/app/src/main/res/layout/activity_grocery_list.xml @@ -12,30 +12,45 @@ + app:layout_constraintVertical_bias="1" + app:layout_constraintHorizontal_bias="1"/> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_location.xml b/app/src/main/res/layout/activity_location.xml deleted file mode 100644 index f5d8d19..0000000 --- a/app/src/main/res/layout/activity_location.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - -