Skip to content

Commit

Permalink
Improved address search bar: now presents the address of the current …
Browse files Browse the repository at this point in the history
…location.

Separated MapScreen into smaller components to improve readability.
  • Loading branch information
andre-j3sus committed Apr 30, 2024
1 parent 9ff7016 commit 1ec07ff
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import org.springframework.stereotype.Repository
import pt.ulisboa.ist.pharmacist.domain.medicines.Medicine
import pt.ulisboa.ist.pharmacist.domain.pharmacies.Location
import pt.ulisboa.ist.pharmacist.repository.MemDataSource
import pt.ulisboa.ist.pharmacist.service.medicines.dtos.MedicineWithClosestPharmacyDto
import pt.ulisboa.ist.pharmacist.service.medicines.dtos.MedicineDto
import pt.ulisboa.ist.pharmacist.service.medicines.dtos.MedicineWithClosestPharmacyDto
import pt.ulisboa.ist.pharmacist.service.pharmacies.dtos.PharmacyDto
import pt.ulisboa.ist.pharmacist.service.utils.paginate

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package pt.ulisboa.ist.pharmacist.service.users

import java.util.UUID
import org.springframework.stereotype.Service
import pt.ulisboa.ist.pharmacist.domain.users.AccessToken
import pt.ulisboa.ist.pharmacist.domain.users.User
Expand All @@ -15,6 +14,7 @@ import pt.ulisboa.ist.pharmacist.service.users.dtos.UserDto
import pt.ulisboa.ist.pharmacist.service.users.dtos.login.LoginOutputDto
import pt.ulisboa.ist.pharmacist.service.users.dtos.register.RegisterOutputDto
import pt.ulisboa.ist.pharmacist.service.utils.HashingUtils
import java.util.UUID

/**
* Service that handles the business logic of the users.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ fun MedicineScreen(

IconButton(
modifier = Modifier,
onClick =toggleMedicineNotification,
onClick = toggleMedicineNotification,
) {
Icon(
if (notificationsActive) Icons.Rounded.NotificationsOff else Icons.Rounded.NotificationsActive, // Icons.Rounded.NotificationsActive
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package pt.ulisboa.ist.pharmacist.ui.screens.pharmacyMap

import android.graphics.Bitmap
import android.location.Geocoder
import android.os.Build
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.ui.res.stringResource
import androidx.annotation.RequiresApi
import androidx.lifecycle.lifecycleScope
import com.google.android.libraries.places.api.Places
import kotlinx.coroutines.launch
Expand All @@ -16,13 +17,23 @@ import pt.ulisboa.ist.pharmacist.ui.screens.pharmacy.PharmacyActivity
import pt.ulisboa.ist.pharmacist.ui.screens.shared.ImageHandlingUtils
import pt.ulisboa.ist.pharmacist.ui.screens.shared.hasCameraPermission
import pt.ulisboa.ist.pharmacist.ui.screens.shared.hasLocationPermission
import pt.ulisboa.ist.pharmacist.ui.screens.shared.viewModelInit

/**
* Activity for the [PharmacyMapScreen].
*/
class PharmacyMapActivity : PharmacistActivity() {

private val viewModel by getViewModel(::PharmacyMapViewModel)
private val viewModel by viewModelInit {
Places.initialize(this, getString(R.string.google_maps_key))

PharmacyMapViewModel(
dependenciesContainer.pharmacistService,
dependenciesContainer.sessionManager,
placesClient = Places.createClient(this),
geoCoder = Geocoder(this)
)
}

private val imageResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
Expand All @@ -49,7 +60,6 @@ class PharmacyMapActivity : PharmacistActivity() {
}
}


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -62,10 +72,6 @@ class PharmacyMapActivity : PharmacistActivity() {
viewModel.loadPharmacyList()

setContent {
Places.initialize(this@PharmacyMapActivity, stringResource(R.string.google_maps_key))
viewModel.placesClient = Places.createClient(this@PharmacyMapActivity)
viewModel.geoCoder = Geocoder(this@PharmacyMapActivity)

PharmacyMapScreen(
followMyLocation = viewModel.followMyLocation,
hasLocationPermission = viewModel.hasLocationPermission,
Expand Down Expand Up @@ -96,7 +102,8 @@ class PharmacyMapActivity : PharmacistActivity() {
setPosition = { location -> viewModel.setPosition(location) },
locationAutofill = viewModel.locationAutofill,
onSearchPlaces = { query -> viewModel.searchPlaces(query) },
onPlaceClick = { placeId -> viewModel.onPlaceClick(placeId) }
onPlaceClick = { placeId -> viewModel.onPlaceClick(placeId) },
searchQuery = viewModel.searchQuery
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,22 @@ import pt.ulisboa.ist.pharmacist.ui.screens.pharmacyMap.components.PermissionScr
/**
* Pharmacy screen.
*
* @param hasLocationPermission true if the app has the necessary permissions, false otherwise
* @param onPharmacyDetailsClick callback to be invoked when the user clicks on the pharmacy details button
* @param pharmacies list of pharmacies to display
* @param mapProperties properties of the map
* @param cameraPositionState of the camera position
* @param onPharmacyDetailsClick callback to be invoked when the user clicks on the pharmacy details button
* @param setFollowMyLocation callback to be invoked when the user clicks on the follow my location button
* @param setPosition callback to be invoked when the position is to be changed
* @param followMyLocation if the map should follow the user's location.
* @param hasLocationPermission if the user has location permission.
* @param hasCameraPermission if the user has camera permission.
* @param mapProperties the map properties.
* @param cameraPositionState the camera position state.
* @param pharmacies the pharmacies.
* @param onPharmacyDetailsClick the callback to be called when a pharmacy is clicked.
* @param onAddPictureButtonClick the callback to be called when the add picture button is clicked.
* @param onAddPharmacyFinishClick the callback to be called when the add pharmacy finish button is clicked.
* @param onAddPharmacyCancelClick the callback to be called when the add pharmacy cancel button is clicked.
* @param newPharmacyPhoto the new pharmacy photo.
* @param setFollowMyLocation the callback to be called when the follow my location button is clicked.
* @param setPosition the callback to be called when the position is set.
* @param locationAutofill the location autofill.
* @param onSearchPlaces the callback to be called when the search places button is clicked.
* @param onPlaceClick the callback to be called when a place is clicked.
*/
@Composable
fun PharmacyMapScreen(
Expand All @@ -51,7 +59,8 @@ fun PharmacyMapScreen(
setPosition: (LatLng) -> Unit,
locationAutofill: MutableList<PharmacyMapViewModel.AutocompleteResult>,
onSearchPlaces: (String) -> Unit,
onPlaceClick: (String) -> Unit
onPlaceClick: (PharmacyMapViewModel.AutocompleteResult) -> Unit,
searchQuery: String
) {
PharmacistScreen {
Column(
Expand All @@ -76,7 +85,8 @@ fun PharmacyMapScreen(
setPosition = setPosition,
locationAutofill = locationAutofill,
onSearchPlaces = onSearchPlaces,
onPlaceClick = onPlaceClick
onPlaceClick = onPlaceClick,
searchQuery = searchQuery
)
else
PermissionScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package pt.ulisboa.ist.pharmacist.ui.screens.pharmacyMap
import android.annotation.SuppressLint
import android.content.Context
import android.location.Geocoder
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
Expand Down Expand Up @@ -36,12 +38,16 @@ import pt.ulisboa.ist.pharmacist.ui.screens.shared.ImageHandlingUtils
*
* @property pharmacistService the service used to handle the pharmacist game
* @property sessionManager the manager used to handle the user session
* @property placesClient the client used to interact with the Places API
* @property geoCoder the geocoder used to get the location of an address
*
* @property state the current state of the view model
*/
class PharmacyMapViewModel(
pharmacistService: PharmacistService,
sessionManager: SessionManager,
val placesClient: PlacesClient,
val geoCoder: Geocoder
) : PharmacistViewModel(pharmacistService, sessionManager) {

var pharmacyPhotoUrl by mutableStateOf<String?>(null)
Expand All @@ -61,11 +67,20 @@ class PharmacyMapViewModel(
var followMyLocation by mutableStateOf(false)

val mapProperties by mutableStateOf(
MapProperties(
isMyLocationEnabled = true,
)
MapProperties(isMyLocationEnabled = true)
)

private var searchQueryJob: Job? = null
val locationAutofill = mutableListOf<AutocompleteResult>()
var searchQuery by mutableStateOf("")
private set

/**
* Uploads the box photo to the server.
*
* @param boxPhotoData the data of the box photo
* @param mediaType the media type of the box photo
*/
fun uploadBoxPhoto(boxPhotoData: ByteArray, mediaType: MediaType) = viewModelScope.launch {
ImageHandlingUtils.uploadBoxPhoto(boxPhotoData, mediaType, pharmacistService)
?.let {
Expand Down Expand Up @@ -120,15 +135,35 @@ class PharmacyMapViewModel(
}
}

/**
* Sets the position of the camera and the search query.
*
* @param latLng the latitude and longitude of the position
*/
fun setPosition(latLng: LatLng) = viewModelScope.launch {
followMyLocation = false
cameraPositionState.animate(
CameraUpdateFactory.newCameraPosition(
CameraPosition.fromLatLngZoom(latLng, cameraPositionState.position.zoom)
)
)

val addresses = geoCoder.getFromLocation(
latLng.latitude,
latLng.longitude,
1
)

if (addresses?.isNotEmpty() == true)
searchQuery = addresses[0].getAddressLine(0)
}

/**
* Adds a pharmacy to the server.
*
* @param name the name of the pharmacy
* @param location the location of the pharmacy
*/
fun addPharmacy(name: String, location: Location) {
if (pharmacyPhotoUrl == null) {
Log.e("AddPharmacy", "Box photo URL is null")
Expand All @@ -153,14 +188,16 @@ class PharmacyMapViewModel(
}
}

private var job: Job? = null
lateinit var placesClient: PlacesClient
lateinit var geoCoder: Geocoder
val locationAutofill = mutableListOf<AutocompleteResult>()

/**
* Searches for places based on a query and updates the list of autofill results.
*
* @param query the query to search for
*/
fun searchPlaces(query: String) {
job?.cancel()
job = viewModelScope.launch {
searchQuery = query
searchQueryJob?.cancel()
searchQueryJob = viewModelScope.launch {
val request = FindAutocompletePredictionsRequest
.builder()
.setQuery(query)
Expand All @@ -186,9 +223,15 @@ class PharmacyMapViewModel(
}


fun onPlaceClick(placeId: String) {
/**
* Handles the click on a place in the autofill list.
*
* @param place the place that was clicked
*/
fun onPlaceClick(place: AutocompleteResult) {
searchQuery = place.address
val placeFields = listOf(Place.Field.LAT_LNG)
val request = FetchPlaceRequest.newInstance(placeId, placeFields)
val request = FetchPlaceRequest.newInstance(place.placeId, placeFields)
placesClient.fetchPlace(request)
.addOnSuccessListener {
if (it != null) {
Expand All @@ -204,13 +247,19 @@ class PharmacyMapViewModel(
}
}


enum class PharmacyMapState {
UNLOADED,
LOADING,
LOADED
}

/**
* An autocomplete result.
*
* @property address the address of the result
* @property placeId the ID of the place
*/
data class AutocompleteResult(
val address: String,
val placeId: String
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package pt.ulisboa.ist.pharmacist.ui.screens.pharmacyMap.components

import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Cancel
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import pt.ulisboa.ist.pharmacist.R

/**
* Button to add a pharmacy to the map.
*
* @param addingPharmacy whether the user is currently adding a pharmacy
* @param onClick callback to be invoked when the user clicks on the button
*/
@Composable
fun BoxScope.AddPharmacyButton(
addingPharmacy: Boolean,
onClick: () -> Unit
) {
ExtendedFloatingActionButton(
onClick = onClick,
containerColor = MaterialTheme.colorScheme.primary,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(24.dp),
icon = {
Icon(
if (!addingPharmacy) Icons.Rounded.Add else Icons.Rounded.Cancel,
if (!addingPharmacy)
stringResource(R.string.pharmacyMap_addPharmacy_button_text)
else
stringResource(R.string.pharmacyMap_cancel_button_text),
)
},
text = {
Text(
if (!addingPharmacy)
stringResource(R.string.pharmacyMap_addPharmacy_button_description)
else
stringResource(R.string.pharmacyMap_cancel_button_description)
)
}
)
}
Loading

0 comments on commit 1ec07ff

Please sign in to comment.